// Copyright 2012 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/sync/test/integration/passwords_helper.h"

#include <sstream>
#include <utility>

#include "base/base64.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/strings/escape.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/time/time.h"
#include "chrome/browser/password_manager/factories/account_password_store_factory.h"
#include "chrome/browser/password_manager/factories/profile_password_store_factory.h"
#include "chrome/browser/sync/test/integration/sync_datatype_helper.h"
#include "components/password_manager/core/browser/password_form.h"
#include "components/password_manager/core/browser/password_manager_test_utils.h"
#include "components/password_manager/core/browser/password_store/password_form_converters.h"
#include "components/password_manager/core/browser/password_store/password_store_consumer.h"
#include "components/password_manager/core/browser/password_store/password_store_interface.h"
#include "components/password_manager/core/browser/sync/password_proto_utils.h"
#include "components/sync/engine/loopback_server/persistent_unique_client_entity.h"
#include "components/sync/model/crypto/key_derivation_params.h"
#include "components/sync/nigori/cryptographer_impl.h"
#include "components/sync/protocol/entity_specifics.pb.h"
#include "components/sync/protocol/password_specifics.pb.h"
#include "components/sync/service/sync_service_impl.h"
#include "content/public/test/test_utils.h"
#include "url/gurl.h"

using password_manager::PasswordForm;
using password_manager::PasswordStoreInterface;
using sync_datatype_helper::test;

namespace {

const char kFakeSignonRealm[] = "http://fake-signon-realm.google.com/";
const char kIndexedFakeOrigin[] = "http://fake-signon-realm.google.com/%d";

class PasswordStoreConsumerHelper
    : public password_manager::PasswordStoreConsumer {
 public:
  PasswordStoreConsumerHelper() = default;

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

  void OnGetPasswordStoreResultsOrErrorFrom(
      password_manager::PasswordStoreInterface* store,
      password_manager::LoginsResultOrError results_or_error) override {
    if (std::holds_alternative<password_manager::PasswordStoreBackendError>(
            results_or_error)) {
      result_ = std::vector<PasswordForm>();
    } else {
      result_ = password_manager::ToPasswordForms(
          std::get<password_manager::LoginsResult>(
              std::move(results_or_error)));
    }
    run_loop_.Quit();
  }

  std::vector<std::unique_ptr<PasswordForm>> WaitForResult() {
    DCHECK(!run_loop_.running());
    run_loop_.Run();
    std::vector<std::unique_ptr<PasswordForm>> unique_results;
    unique_results.reserve(result_.size());
    for (auto& form : result_) {
      unique_results.push_back(std::make_unique<PasswordForm>(std::move(form)));
    }
    return unique_results;
  }

  base::WeakPtr<password_manager::PasswordStoreConsumer> GetWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

 private:
  // This RunLoop uses kNestableTasksAllowed because it runs nested within
  // another RunLoop.
  // TODO(crbug.com/41486990): consider changing this to PasswordStoreInterface
  // observer to avoid nested run loops.
  base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
  std::vector<PasswordForm> result_;
  base::WeakPtrFactory<PasswordStoreConsumerHelper> weak_ptr_factory_{this};
};

sync_pb::EntitySpecifics EncryptPasswordSpecifics(
    const sync_pb::PasswordSpecificsData& password_data,
    const std::string& passphrase,
    const syncer::KeyDerivationParams& key_derivation_params) {
  std::unique_ptr<syncer::CryptographerImpl> cryptographer =
      syncer::CryptographerImpl::FromSingleKeyForTesting(passphrase,
                                                         key_derivation_params);
  sync_pb::EntitySpecifics encrypted_specifics;
  encrypted_specifics.mutable_password()
      ->mutable_unencrypted_metadata()
      ->set_url(password_data.signon_realm());
  bool result = cryptographer->Encrypt(
      password_data,
      encrypted_specifics.mutable_password()->mutable_encrypted());
  DCHECK(result);
  return encrypted_specifics;
}

std::string GetClientTag(const sync_pb::PasswordSpecificsData& password_data) {
  return base::EscapePath(GURL(password_data.origin()).spec()) + "|" +
         base::EscapePath(password_data.username_element()) + "|" +
         base::EscapePath(password_data.username_value()) + "|" +
         base::EscapePath(password_data.password_element()) + "|" +
         base::EscapePath(password_data.signon_realm());
}

}  // namespace

namespace passwords_helper {

std::vector<std::unique_ptr<PasswordForm>> GetLogins(
    PasswordStoreInterface* store) {
  EXPECT_TRUE(store);
  PasswordStoreConsumerHelper consumer;
  store->GetAutofillableLogins(consumer.GetWeakPtr());
  return consumer.WaitForResult();
}

std::vector<std::unique_ptr<PasswordForm>> GetAllLogins(
    PasswordStoreInterface* store) {
  EXPECT_TRUE(store);
  PasswordStoreConsumerHelper consumer;
  store->GetAllLogins(consumer.GetWeakPtr());
  return consumer.WaitForResult();
}

void RemoveLogins(PasswordStoreInterface* store) {
  // Null Time values enforce unbounded deletion in both direction
  store->RemoveLoginsCreatedBetween(FROM_HERE,
                                    /*delete_begin=*/base::Time(),
                                    /*delete_end=*/base::Time::Max());
}
PasswordStoreInterface* GetProfilePasswordStoreInterface(int index) {
  return ProfilePasswordStoreFactory::GetForProfile(
             test()->GetProfile(index), ServiceAccessType::IMPLICIT_ACCESS)
      .get();
}

PasswordStoreInterface* GetAccountPasswordStoreInterface(int index) {
  return AccountPasswordStoreFactory::GetForProfile(
             test()->GetProfile(index), ServiceAccessType::IMPLICIT_ACCESS)
      .get();
}

password_manager::PasswordStoreInterface* GetPasswordStoreInterface(
    int index,
    PasswordForm::Store store) {
  switch (store) {
    case PasswordForm::Store::kNotSet:
      NOTREACHED();
    case PasswordForm::Store::kProfileStore:
      return GetProfilePasswordStoreInterface(index);
    case PasswordForm::Store::kAccountStore:
      return GetAccountPasswordStoreInterface(index);
  }
}

bool ProfilesContainSamePasswordForms(int index_a,
                                      int index_b,
                                      PasswordForm::Store store) {
  std::vector<std::unique_ptr<PasswordForm>> forms_a =
      GetLogins(GetPasswordStoreInterface(index_a, store));
  std::vector<std::unique_ptr<PasswordForm>> forms_b =
      GetLogins(GetPasswordStoreInterface(index_b, store));

  std::ostringstream mismatch_details_stream;
  bool is_matching = password_manager::ContainsEqualPasswordFormsUnordered(
      forms_a, forms_b, &mismatch_details_stream);
  if (!is_matching) {
    VLOG(1) << "Password forms in Profile " << index_a
            << " (listed as 'expected forms' below)"
            << " do not match those in Profile " << index_b
            << " (listed as 'actual forms' below)";
    VLOG(1) << mismatch_details_stream.str();
  }
  return is_matching;
}

bool AllProfilesContainSamePasswordForms(PasswordForm::Store store) {
  for (int i = 1; i < test()->num_clients(); ++i) {
    if (!ProfilesContainSamePasswordForms(0, i, store)) {
      DVLOG(1) << "Profile " << i
               << " does not contain the same password"
                  " forms as Profile 0.";
      return false;
    }
  }
  return true;
}

int GetPasswordCount(int index, PasswordForm::Store store) {
  return GetLogins(GetPasswordStoreInterface(index, store)).size();
}

PasswordForm CreateTestPasswordForm(int index, PasswordForm::Store store) {
  PasswordForm form;
  form.signon_realm = kFakeSignonRealm;
  form.url = GURL(base::StringPrintf(kIndexedFakeOrigin, index));
  form.username_value =
      base::ASCIIToUTF16(base::StringPrintf("username%d", index));
  form.password_value =
      base::ASCIIToUTF16(base::StringPrintf("password%d", index));
  form.date_created = base::Time::Now();
  form.in_store = store;
  return form;
}

void InjectEncryptedServerPassword(
    const password_manager::PasswordForm& form,
    const std::string& encryption_passphrase,
    const syncer::KeyDerivationParams& key_derivation_params,
    fake_server::FakeServer* fake_server) {
  sync_pb::PasswordSpecificsData password_data =
      password_manager::SpecificsFromStoredCredential(
          password_manager::FromPasswordForm(form), /*base_password_data=*/{})
          .client_only_encrypted_data();
  InjectEncryptedServerPassword(password_data, encryption_passphrase,
                                key_derivation_params, fake_server);
}

void InjectEncryptedServerPassword(
    const sync_pb::PasswordSpecificsData& password_data,
    const std::string& encryption_passphrase,
    const syncer::KeyDerivationParams& key_derivation_params,
    fake_server::FakeServer* fake_server) {
  DCHECK(fake_server);
  const sync_pb::EntitySpecifics encrypted_specifics = EncryptPasswordSpecifics(
      password_data, encryption_passphrase, key_derivation_params);
  fake_server->InjectEntity(
      syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
          /*non_unique_name=*/"encrypted", GetClientTag(password_data),
          encrypted_specifics,
          /*creation_time=*/0, /*last_modified_time=*/0));
}

void InjectKeystoreEncryptedServerPassword(
    const password_manager::PasswordForm& form,
    fake_server::FakeServer* fake_server) {
  sync_pb::PasswordSpecificsData password_data =
      password_manager::SpecificsFromStoredCredential(
          password_manager::FromPasswordForm(form), /*base_password_data=*/{})
          .client_only_encrypted_data();
  InjectKeystoreEncryptedServerPassword(password_data, fake_server);
}

void InjectKeystoreEncryptedServerPassword(
    const sync_pb::PasswordSpecificsData& password_data,
    fake_server::FakeServer* fake_server) {
  InjectEncryptedServerPassword(
      password_data, base::Base64Encode(fake_server->GetKeystoreKeys().back()),
      syncer::KeyDerivationParams::CreateForPbkdf2(), fake_server);
}

}  // namespace passwords_helper

PasswordSyncActiveChecker::PasswordSyncActiveChecker(
    syncer::SyncServiceImpl* service)
    : SingleClientStatusChangeChecker(service) {}

PasswordSyncActiveChecker::~PasswordSyncActiveChecker() = default;

bool PasswordSyncActiveChecker::IsExitConditionSatisfied(std::ostream* os) {
  return service()->GetActiveDataTypes().Has(syncer::PASSWORDS);
}

PasswordSyncInactiveChecker::PasswordSyncInactiveChecker(
    syncer::SyncServiceImpl* service)
    : SingleClientStatusChangeChecker(service) {}

PasswordSyncInactiveChecker::~PasswordSyncInactiveChecker() = default;

bool PasswordSyncInactiveChecker::IsExitConditionSatisfied(std::ostream* os) {
  return !service()->GetActiveDataTypes().Has(syncer::PASSWORDS);
}

SamePasswordFormsChecker::SamePasswordFormsChecker(PasswordForm::Store store)
    : MultiClientStatusChangeChecker(
          sync_datatype_helper::test()->GetSyncServices()),
      store_(store) {}

SamePasswordFormsChecker::~SamePasswordFormsChecker() = default;

// This method needs protection against re-entrancy.
//
// This function indirectly calls GetLogins(), which starts a RunLoop on the UI
// thread.  This can be a problem, since the next task to execute could very
// well contain a SyncServiceObserver::OnStateChanged() event, which would
// trigger another call to this here function, and start another layer of
// nested RunLoops.  That makes the StatusChangeChecker's Quit() method
// ineffective.
//
// The work-around is to not allow re-entrancy.  But we can't just drop
// IsExitConditionSatisifed() calls if one is already in progress.  Instead, we
// set a flag to ask the current execution of IsExitConditionSatisfied() to be
// re-run.  This ensures that the return value is always based on the most
// up-to-date state.
bool SamePasswordFormsChecker::IsExitConditionSatisfied(std::ostream* os) {
  *os << "Waiting for matching passwords";

  if (in_progress_) {
    LOG(WARNING) << "Setting flag and returning early to prevent nesting.";
    needs_recheck_ = true;
    return false;
  }

  // Keep retrying until we get a good reading.
  bool result = false;
  in_progress_ = true;
  do {
    needs_recheck_ = false;
    result = passwords_helper::AllProfilesContainSamePasswordForms(store_);
  } while (needs_recheck_);
  in_progress_ = false;
  return result;
}

PasswordFormsChecker::PasswordFormsChecker(
    int index,
    const std::vector<password_manager::PasswordForm>& expected_forms,
    PasswordForm::Store store)
    : SingleClientStatusChangeChecker(
          sync_datatype_helper::test()->GetSyncService(index)),
      index_(index),
      store_(store) {
  for (const password_manager::PasswordForm& password_form : expected_forms) {
    expected_forms_.push_back(
        std::make_unique<password_manager::PasswordForm>(password_form));
  }
}

PasswordFormsChecker::~PasswordFormsChecker() = default;

// This method uses the same re-entrancy prevention trick as
// the SamePasswordFormsChecker.
bool PasswordFormsChecker::IsExitConditionSatisfied(std::ostream* os) {
  if (in_progress_) {
    LOG(WARNING) << "Setting flag and returning early to prevent nesting.";
    *os << "Setting flag and returning early to prevent nesting.";
    needs_recheck_ = true;
    return false;
  }

  // Keep retrying until we get a good reading.
  bool result = false;
  in_progress_ = true;
  do {
    needs_recheck_ = false;
    result = IsExitConditionSatisfiedImpl(os);
  } while (needs_recheck_);
  in_progress_ = false;
  return result;
}

bool PasswordFormsChecker::IsExitConditionSatisfiedImpl(std::ostream* os) {
  std::vector<std::unique_ptr<PasswordForm>> forms =
      passwords_helper::GetLogins(
          passwords_helper::GetPasswordStoreInterface(index_, store_));

  std::ostringstream mismatch_details_stream;
  bool is_matching = password_manager::ContainsEqualPasswordFormsUnordered(
      expected_forms_, forms, &mismatch_details_stream);
  if (!is_matching) {
    *os << "Profile " << index_
        << " does not contain the same Password forms as expected. "
        << mismatch_details_stream.str();
  }
  return is_matching;
}

ServerPasswordsEqualityChecker::ServerPasswordsEqualityChecker(
    const std::vector<password_manager::PasswordForm>& expected_forms,
    const std::string& encryption_passphrase,
    const syncer::KeyDerivationParams& key_derivation_params)
    : cryptographer_(syncer::CryptographerImpl::FromSingleKeyForTesting(
          encryption_passphrase,
          key_derivation_params)) {
  for (const password_manager::PasswordForm& password_form : expected_forms) {
    expected_forms_.push_back(
        std::make_unique<password_manager::PasswordForm>(password_form));
    // |in_store| field is specific for the clients, clean it up, since server
    // specifics don't have it.
    expected_forms_.back()->in_store =
        password_manager::PasswordForm::Store::kNotSet;
  }
}

ServerPasswordsEqualityChecker::~ServerPasswordsEqualityChecker() = default;

bool ServerPasswordsEqualityChecker::IsExitConditionSatisfied(
    std::ostream* os) {
  *os << "Waiting for server passwords to match the expected value.";

  std::vector<sync_pb::SyncEntity> entities =
      fake_server()->GetSyncEntitiesByDataType(syncer::PASSWORDS);
  if (expected_forms_.size() != entities.size()) {
    *os << "Server doesn't not contain same amount of passwords ("
        << entities.size() << ") as expected (" << expected_forms_.size()
        << ").";
    return false;
  }

  std::vector<std::unique_ptr<password_manager::PasswordForm>>
      server_password_forms;
  for (const auto& entity : entities) {
    if (!entity.specifics().has_password()) {
      *os << "Server stores corrupted password.";
      return false;
    }

    sync_pb::PasswordSpecificsData decrypted;
    if (!cryptographer_->Decrypt(entity.specifics().password().encrypted(),
                                 &decrypted)) {
      *os << "Can't decrypt server password.";
      return false;
    }
    server_password_forms.push_back(
        std::make_unique<password_manager::PasswordForm>(
            password_manager::ToPasswordForm(
                password_manager::StoredCredentialFromSpecifics(decrypted))));
  }

  std::ostringstream mismatch_details_stream;
  bool is_matching = password_manager::ContainsEqualPasswordFormsUnordered(
      expected_forms_, server_password_forms, &mismatch_details_stream);
  if (!is_matching) {
    *os << "Server does not contain the same Password forms as expected. "
        << mismatch_details_stream.str();
  }
  return is_matching;
}

PasswordFormsAddedChecker::PasswordFormsAddedChecker(
    password_manager::PasswordStoreInterface* password_store,
    size_t expected_new_password_forms)
    : password_store_(password_store),
      expected_new_password_forms_(expected_new_password_forms) {
  password_store_->AddObserver(this);
}

PasswordFormsAddedChecker::~PasswordFormsAddedChecker() {
  password_store_->RemoveObserver(this);
}

bool PasswordFormsAddedChecker::IsExitConditionSatisfied(std::ostream* os) {
  *os << "Waiting for " << expected_new_password_forms_
      << " passwords added to the store. ";

  *os << "Current number of added password forms to the store: "
      << num_added_passwords_;
  return num_added_passwords_ == expected_new_password_forms_;
}

void PasswordFormsAddedChecker::OnLoginsChanged(
    password_manager::PasswordStoreInterface* store,
    const password_manager::PasswordStoreChangeList& changes) {
  for (const password_manager::PasswordStoreChange& change : changes) {
    if (change.type() == password_manager::PasswordStoreChange::ADD) {
      num_added_passwords_++;
    }
  }

  CheckExitCondition();
}

void PasswordFormsAddedChecker::OnLoginsRetained(
    password_manager::PasswordStoreInterface* store,
    const std::vector<password_manager::StoredCredential>& retained_passwords) {
  // Not used.
}
