chromium/chrome/browser/sync/test/integration/single_client_wifi_configuration_sync_test.cc

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

#include <memory>
#include <string>
#include <vector>

#include "ash/public/cpp/network_config_service.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/sync/test/integration/single_client_status_change_checker.h"
#include "chrome/browser/sync/test/integration/status_change_checker.h"
#include "chrome/browser/sync/test/integration/sync_engine_stopped_checker.h"
#include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chromeos/ash/components/dbus/shill/shill_profile_client.h"
#include "chromeos/ash/components/sync_wifi/network_identifier.h"
#include "chromeos/ash/components/sync_wifi/test_data_generator.h"
#include "chromeos/services/network_config/public/cpp/cros_network_config_observer.h"
#include "components/sync/engine/loopback_server/persistent_unique_client_entity.h"
#include "components/sync/nigori/cryptographer_impl.h"
#include "components/sync/protocol/wifi_configuration_specifics.pb.h"
#include "components/sync/test/fake_server.h"
#include "components/sync/test/nigori_test_utils.h"
#include "content/public/test/browser_test.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gmock/include/gmock/gmock.h"

namespace {

std::string GetClientTag(const sync_pb::WifiConfigurationSpecifics& specifics) {
  return ash::sync_wifi::NetworkIdentifier::FromProto(specifics)
      .SerializeToString();
}

void InjectKeystoreEncryptedServerWifiConfiguration(
    fake_server::FakeServer* fake_server,
    const sync_pb::WifiConfigurationSpecifics& unencrypted_specifics) {
  sync_pb::EntitySpecifics wrapped_unencrypted_specifics;
  *wrapped_unencrypted_specifics.mutable_wifi_configuration() =
      unencrypted_specifics;

  sync_pb::EntitySpecifics encrypted_specifics;
  const syncer::KeyParamsForTesting keystore_key_params =
      syncer::KeystoreKeyParamsForTesting(
          fake_server->GetKeystoreKeys().back());
  auto cryptographer = syncer::CryptographerImpl::FromSingleKeyForTesting(
      keystore_key_params.password, keystore_key_params.derivation_params);
  bool encrypt_result = cryptographer->Encrypt(
      wrapped_unencrypted_specifics, encrypted_specifics.mutable_encrypted());
  *encrypted_specifics.mutable_wifi_configuration() =
      sync_pb::WifiConfigurationSpecifics();
  DCHECK(encrypt_result);

  fake_server->InjectEntity(
      syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
          /*non_unique_name=*/"encrypted", GetClientTag(unencrypted_specifics),
          encrypted_specifics,
          /*creation_time=*/0, /*last_modified_time=*/0));
}

// Observes Shill networks state via IPC. Used to wait until wifi configuration
// becomes locally available.
class LocalWifiConfigurationChecker
    : public StatusChangeChecker,
      public chromeos::network_config::CrosNetworkConfigObserver {
 public:
  LocalWifiConfigurationChecker(
      mojo::Remote<chromeos::network_config::mojom::CrosNetworkConfig>*
          remote_cros_network_config,
      const std::string& expected_ssid)
      : expected_ssid_(expected_ssid),
        remote_cros_network_config_(remote_cros_network_config) {
    DCHECK(remote_cros_network_config_);
    (*remote_cros_network_config_)
        ->AddObserver(receiver_.BindNewPipeAndPassRemote());
    OnNetworkStateListChanged();
  }

  LocalWifiConfigurationChecker(const LocalWifiConfigurationChecker& other) =
      delete;
  LocalWifiConfigurationChecker& operator=(
      const LocalWifiConfigurationChecker& other) = delete;
  ~LocalWifiConfigurationChecker() override = default;

  // CrosNetworkConfigObserver implementation.
  void OnNetworkStateListChanged() override {
    (*remote_cros_network_config_)
        ->GetNetworkStateList(
            chromeos::network_config::mojom::NetworkFilter::New(
                chromeos::network_config::mojom::FilterType::kConfigured,
                chromeos::network_config::mojom::NetworkType::kWiFi,
                /*limit=*/0),
            base::BindOnce(&LocalWifiConfigurationChecker::OnGetNetworkList,
                           weak_ptr_factory_.GetWeakPtr()));
  }

  // StatusChangeChecker implementation.
  bool IsExitConditionSatisfied(std::ostream* os) override {
    *os << "Waiting for network with ssid: " << expected_ssid_;
    for (const auto& network : networks_) {
      if (network->type_state->get_wifi()->ssid == expected_ssid_) {
        return true;
      }
    }
    return false;
  }

  void OnGetNetworkList(
      std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
          networks) {
    networks_ = std::move(networks);
    CheckExitCondition();
  }

 private:
  const std::string expected_ssid_;
  std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
      networks_;

  raw_ptr<mojo::Remote<chromeos::network_config::mojom::CrosNetworkConfig>>
      remote_cros_network_config_;
  mojo::Receiver<chromeos::network_config::mojom::CrosNetworkConfigObserver>
      receiver_{this};
  base::WeakPtrFactory<LocalWifiConfigurationChecker> weak_ptr_factory_{this};
};

// TODO(crbug.com/1077152): add more tests.
class SingleClientWifiConfigurationSyncTest : public SyncTest {
 public:
  SingleClientWifiConfigurationSyncTest() : SyncTest(SINGLE_CLIENT) {}
  SingleClientWifiConfigurationSyncTest(
      const SingleClientWifiConfigurationSyncTest&) = delete;
  SingleClientWifiConfigurationSyncTest& operator=(
      const SingleClientWifiConfigurationSyncTest&) = delete;
  ~SingleClientWifiConfigurationSyncTest() override = default;

  void SetUpOnMainThread() override {
    ash::GetNetworkConfigService(
        remote_cros_network_config_.BindNewPipeAndPassReceiver());

    SyncTest::SetUpOnMainThread();
  }

  void SetupShill() {
    // TODO(crbug.com/1077152): figure out where |userhash| is hardcoded and use
    // some shared constant here.
    ash::ShillProfileClient::Get()->GetTestInterface()->AddProfile(
        GetProfile(0)->GetPath().value(), /*userhash=*/"test-user");
  }

  mojo::Remote<chromeos::network_config::mojom::CrosNetworkConfig>*
  remote_cros_network_config() {
    return &remote_cros_network_config_;
  }

 private:
  mojo::Remote<chromeos::network_config::mojom::CrosNetworkConfig>
      remote_cros_network_config_;
};

IN_PROC_BROWSER_TEST_F(SingleClientWifiConfigurationSyncTest,
                       ShouldDownloadSingleWifiConfiguration) {
  const std::string kTestSsid = "test_wifi";
  InjectKeystoreEncryptedServerWifiConfiguration(
      GetFakeServer(),
      /*unencrypted_specifics=*/ash::sync_wifi::GenerateTestWifiSpecifics(
          ash::sync_wifi::GeneratePskNetworkId(kTestSsid)));

  ASSERT_TRUE(SetupSync());
  SetupShill();

  EXPECT_TRUE(
      LocalWifiConfigurationChecker(remote_cros_network_config(), kTestSsid)
          .Wait());
}

// Regression test for crbug.com/1318390: the client should clear metadata when
// sync requires it and perform initial sync again (was crashing before the
// fix).
IN_PROC_BROWSER_TEST_F(SingleClientWifiConfigurationSyncTest,
                       ShouldHandleClientDataObsolete) {
  const std::string kTestSsid1 = "test_wifi";
  InjectKeystoreEncryptedServerWifiConfiguration(
      GetFakeServer(),
      /*unencrypted_specifics=*/ash::sync_wifi::GenerateTestWifiSpecifics(
          ash::sync_wifi::GeneratePskNetworkId(kTestSsid1)));

  ASSERT_TRUE(SetupSync());
  SetupShill();
  ASSERT_TRUE(
      LocalWifiConfigurationChecker(remote_cros_network_config(), kTestSsid1)
          .Wait());

  GetFakeServer()->TriggerError(sync_pb::SyncEnums::CLIENT_DATA_OBSOLETE);

  // Trigger sync by making one more change.
  const std::string kTestSsid2 = "test_wifi2";
  InjectKeystoreEncryptedServerWifiConfiguration(
      GetFakeServer(),
      /*unencrypted_specifics=*/ash::sync_wifi::GenerateTestWifiSpecifics(
          ash::sync_wifi::GeneratePskNetworkId(kTestSsid2)));
  ASSERT_TRUE(syncer::SyncEngineStoppedChecker(GetSyncService(0)).Wait());

  // Make server return SUCCESS so that sync can initialize.
  GetFakeServer()->TriggerError(sync_pb::SyncEnums::SUCCESS);
  ASSERT_TRUE(GetClient(0)->AwaitEngineInitialization());

  // Ensure client has both networks.
  EXPECT_TRUE(
      LocalWifiConfigurationChecker(remote_cros_network_config(), kTestSsid1)
          .Wait());
  EXPECT_TRUE(
      LocalWifiConfigurationChecker(remote_cros_network_config(), kTestSsid2)
          .Wait());
}

}  // namespace