chromium/chrome/browser/lacros/sync/sync_explicit_passphrase_client_lacros_unittest.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 "chrome/browser/lacros/sync/sync_explicit_passphrase_client_lacros.h"

#include <utility>

#include "base/observer_list.h"
#include "base/test/task_environment.h"
#include "chromeos/crosapi/mojom/account_manager.mojom.h"
#include "chromeos/crosapi/mojom/sync.mojom.h"
#include "components/account_manager_core/account.h"
#include "components/account_manager_core/account_manager_util.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/sync/chromeos/explicit_passphrase_mojo_utils.h"
#include "components/sync/engine/nigori/key_derivation_params.h"
#include "components/sync/engine/nigori/nigori.h"
#include "components/sync/service/sync_service_observer.h"
#include "components/sync/test/fake_sync_explicit_passphrase_client_ash.h"
#include "components/sync/test/fake_sync_mojo_service.h"
#include "components/sync/test/mock_sync_service.h"
#include "components/sync/test/sync_user_settings_mock.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

using testing::_;
using testing::ByMove;
using testing::NotNull;
using testing::Return;

std::unique_ptr<syncer::Nigori> MakeTestNigoriKey() {
  return syncer::Nigori::CreateByDerivation(
      syncer::KeyDerivationParams::CreateForPbkdf2(), /*password=*/"password");
}

crosapi::mojom::NigoriKeyPtr MakeTestMojoNigoriKey() {
  std::unique_ptr<syncer::Nigori> nigori_key = MakeTestNigoriKey();
  return syncer::NigoriToMojo(*nigori_key);
}

crosapi::mojom::AccountKeyPtr MakeMojoAccountKey(
    const CoreAccountInfo& account_info) {
  return account_manager::ToMojoAccountKey(account_manager::AccountKey(
      account_info.gaia, account_manager::AccountType::kGaia));
}

class SyncExplicitPassphraseClientLacrosTest : public testing::Test {
 public:
  SyncExplicitPassphraseClientLacrosTest() { sync_account_info_.gaia = "user"; }

  void SetUp() override {
    ON_CALL(sync_service_, GetAccountInfo())
        .WillByDefault(Return(sync_account_info_));
    ON_CALL(sync_service_, AddObserver(_))
        .WillByDefault([this](syncer::SyncServiceObserver* observer) {
          sync_service_observers_.AddObserver(observer);
        });
    ON_CALL(sync_service_, RemoveObserver(_))
        .WillByDefault([this](syncer::SyncServiceObserver* observer) {
          sync_service_observers_.RemoveObserver(observer);
        });

    mojo::Remote<crosapi::mojom::SyncExplicitPassphraseClient> client_remote;
    client_ash_.BindReceiver(client_remote.BindNewPipeAndPassReceiver());

    client_ash_.SetExpectedAccountKey(MakeMojoAccountKey(sync_account_info_));

    client_lacros_ = std::make_unique<SyncExplicitPassphraseClientLacros>(
        std::move(client_remote), &sync_service_);
    // Needed to trigger AddObserver() call.
    client_lacros_->FlushMojoForTesting();
  }

  void MimicLacrosPassphraseRequired() {
    ON_CALL(*sync_service_.GetMockUserSettings(), IsPassphraseRequired())
        .WillByDefault(Return(true));
    ON_CALL(*sync_service_.GetMockUserSettings(), IsUsingExplicitPassphrase())
        .WillByDefault(Return(true));
    ON_CALL(*sync_service_.GetMockUserSettings(),
            GetExplicitPassphraseDecryptionNigoriKey())
        .WillByDefault(Return(ByMove(nullptr)));
    for (auto& observer : sync_service_observers_) {
      observer.OnStateChanged(&sync_service_);
    }
  }

  void MimicLacrosPassphraseAvailable() {
    ON_CALL(*sync_service_.GetMockUserSettings(), IsPassphraseRequired())
        .WillByDefault(Return(false));
    ON_CALL(*sync_service_.GetMockUserSettings(), IsUsingExplicitPassphrase())
        .WillByDefault(Return(true));
    ON_CALL(*sync_service_.GetMockUserSettings(),
            GetExplicitPassphraseDecryptionNigoriKey())
        .WillByDefault(MakeTestNigoriKey);
    for (auto& observer : sync_service_observers_) {
      observer.OnStateChanged(&sync_service_);
    }
  }

  SyncExplicitPassphraseClientLacros& client_lacros() {
    return *client_lacros_;
  }

  syncer::FakeSyncExplicitPassphraseClientAsh& client_ash() {
    return client_ash_;
  }

  syncer::SyncUserSettingsMock& user_settings() {
    return *sync_service_.GetMockUserSettings();
  }

 private:
  base::test::SingleThreadTaskEnvironment task_environment_;

  testing::NiceMock<syncer::MockSyncService> sync_service_;
  base::ObserverList<syncer::SyncServiceObserver,
                     /*check_empty=*/true>::Unchecked sync_service_observers_;

  CoreAccountInfo sync_account_info_;
  syncer::FakeSyncExplicitPassphraseClientAsh client_ash_;

  std::unique_ptr<SyncExplicitPassphraseClientLacros> client_lacros_;
};

TEST_F(SyncExplicitPassphraseClientLacrosTest,
       ShouldPassNigoriKeyToAshWhenPassphraseAlreadyAvailable) {
  // Corresponds to scenario, when custom passphrase was enabled in Lacros,
  // Lacros will have passphrase available almost immediately, while Ash will
  // require it only after Sync cycle completion. Lacros should pass passphrase
  // to Ash once it becomes required by Ash.
  MimicLacrosPassphraseAvailable();
  client_ash().MimicPassphraseRequired(MakeTestMojoNigoriKey());
  client_lacros().FlushMojoForTesting();

  EXPECT_TRUE(client_ash().IsSetDecryptionNigoriKeyCalled());
  EXPECT_FALSE(client_ash().IsPassphraseRequired());
}

TEST_F(SyncExplicitPassphraseClientLacrosTest,
       ShouldPassNigoriKeyToAshWhenPassphraseAvailableAfterRequiredByAsh) {
  // Corresponds to scenario, when custom passphrase was enabled on other
  // device, passphrase will become required in both Ash and Lacros roughly at
  // the same time. Once user enters the decryption passphrase in Lacros, it
  // should be passed to Ash.
  client_ash().MimicPassphraseRequired(MakeTestMojoNigoriKey());
  MimicLacrosPassphraseAvailable();
  client_lacros().FlushMojoForTesting();

  EXPECT_TRUE(client_ash().IsSetDecryptionNigoriKeyCalled());
  EXPECT_FALSE(client_ash().IsPassphraseRequired());
}

TEST_F(SyncExplicitPassphraseClientLacrosTest, ShouldGetNigoriKeyFromAsh) {
  MimicLacrosPassphraseRequired();

  EXPECT_CALL(user_settings(),
              SetExplicitPassphraseDecryptionNigoriKey(NotNull()));
  client_ash().MimicPassphraseAvailable(MakeTestMojoNigoriKey());
}

TEST_F(SyncExplicitPassphraseClientLacrosTest,
       ShouldHandleFailedGetNigoriKeyFromAsh) {
  MimicLacrosPassphraseRequired();
  // client_ash() will notify observers that passphrase is available, but expose
  // nullptr when GetDecryptionNigoriKey() is called. Lacros client should
  // handle this nullptr and shouldn't call
  // SyncUserSettings::SetExplicitPassphraseDecryptionNigoriKey().
  EXPECT_CALL(user_settings(), SetExplicitPassphraseDecryptionNigoriKey(_))
      .Times(0);
  client_ash().MimicPassphraseAvailable(/*nigori_key=*/nullptr);
}

TEST_F(SyncExplicitPassphraseClientLacrosTest,
       ShouldHandleFailedGetDecryptionNigoriKeyFromLacros) {
  MimicLacrosPassphraseAvailable();
  // Mimic rare corner case, when IsPassphraseAvailable() false positive
  // detection happens.
  ON_CALL(user_settings(), GetExplicitPassphraseDecryptionNigoriKey())
      .WillByDefault(Return(ByMove(nullptr)));
  client_ash().MimicPassphraseRequired(MakeTestMojoNigoriKey());
  client_lacros().FlushMojoForTesting();

  EXPECT_FALSE(client_ash().IsSetDecryptionNigoriKeyCalled());
}

TEST_F(SyncExplicitPassphraseClientLacrosTest, ShouldNotPassNigoriKeyToAsh) {
  MimicLacrosPassphraseAvailable();
  // client_ash() doesn't notify about passphrase required, client_lacros()
  // shouldn't issue redundant IPC.
  client_lacros().FlushMojoForTesting();
  EXPECT_FALSE(client_ash().IsSetDecryptionNigoriKeyCalled());
}

TEST_F(SyncExplicitPassphraseClientLacrosTest, ShouldNotGetNigoriKeyFromAsh) {
  MimicLacrosPassphraseRequired();
  // client_ash() doesn't notify about passphrase available, client_lacros()
  // shouldn't issue redundant IPC.
  client_lacros().FlushMojoForTesting();
  EXPECT_FALSE(client_ash().IsGetDecryptionNigoriKeyCalled());
}

}  // namespace