// Copyright 2018 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/encryption_helper.h"

#include <string>
#include <vector>

#include "base/base64.h"
#include "base/functional/bind.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "components/sync/base/passphrase_enums.h"
#include "components/sync/service/sync_client.h"
#include "components/sync/service/sync_service_impl.h"
#include "google_apis/gaia/gaia_id.h"
#include "google_apis/gaia/gaia_urls.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace encryption_helper {

namespace {

GURL GetFakeTrustedVaultRetrievalURL(
    const net::test_server::EmbeddedTestServer& test_server,
    const GaiaId& gaia_id,
    const std::vector<uint8_t>& encryption_key,
    int encryption_key_version) {
  // encryption_keys_retrieval.html would populate encryption key to
  // TrustedVaultService service upon loading. Key is provided as part of URL
  // and needs to be encoded with Base64, because it is binary.
  const std::string base64_encoded_key = base::Base64Encode(encryption_key);
  return test_server.GetURL(base::StringPrintf(
      "/sync/encryption_keys_retrieval.html?gaia=%s&key=%s&key_version=%d",
      gaia_id.ToString().c_str(), base64_encoded_key.c_str(),
      encryption_key_version));
}

GURL GetFakeTrustedVaultRecoverabilityURL(
    const net::test_server::EmbeddedTestServer& test_server,
    const GaiaId& gaia_id,
    const std::vector<uint8_t>& public_key) {
  // encryption_keys_recoverability.html would populate `public_key` to
  // TrustedVaultService upon loading. Key is provided as part of URL and needs
  // to be encoded with Base64, because it is binary.
  const std::string base64_encoded_public_key = base::Base64Encode(public_key);
  return test_server.GetURL(base::StringPrintf(
      "/sync/encryption_keys_recoverability.html?%s#%s",
      gaia_id.ToString().c_str(), base64_encoded_public_key.c_str()));
}

// Helper function to install server redirects in the test HTTP server.
std::unique_ptr<net::test_server::HttpResponse> HttpServerRedirect(
    const GURL& from_prefix,
    const GURL& to,
    const net::test_server::HttpRequest& request) {
  if (!base::StartsWith(request.GetURL().spec(), from_prefix.spec())) {
    return nullptr;
  }
  auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
  http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
  http_response->AddCustomHeader("Location", to.spec());
  http_response->set_content_type("text/html");
  http_response->set_content(base::StringPrintf(
      "<html><head></head><body>Redirecting to %s</body></html>",
      to.spec().c_str()));
  return http_response;
}

}  // namespace

void SetupFakeTrustedVaultPages(
    const GaiaId& gaia_id,
    const std::vector<uint8_t>& trusted_vault_key,
    int trusted_vault_key_version,
    const std::vector<uint8_t>& recovery_method_public_key,
    net::test_server::EmbeddedTestServer* test_server) {
  CHECK(test_server);
  // Note that this needs to be installed before the analogous below for
  // retrieval, because they share prefix.
  const GURL recoverability_url = GetFakeTrustedVaultRecoverabilityURL(
      *test_server, gaia_id, recovery_method_public_key);
  test_server->RegisterRequestHandler(base::BindRepeating(
      &HttpServerRedirect,
      /*from_prefix=*/
      GaiaUrls::GetInstance()->SigninChromeSyncKeysRecoverabilityDegradedUrl(
          /*account_index=*/0),
      /*to=*/recoverability_url));

  const GURL retrieval_url = GetFakeTrustedVaultRetrievalURL(
      *test_server, gaia_id, trusted_vault_key, trusted_vault_key_version);
  test_server->RegisterRequestHandler(base::BindRepeating(
      &HttpServerRedirect,
      /*from_prefix=*/
      GaiaUrls::GetInstance()->SigninChromeSyncKeysRetrievalUrl(
          /*account_index=*/0),
      /*to=*/retrieval_url));
}

}  // namespace encryption_helper

ServerPassphraseTypeChecker::ServerPassphraseTypeChecker(
    syncer::PassphraseType expected_passphrase_type)
    : expected_passphrase_type_(expected_passphrase_type) {}

bool ServerPassphraseTypeChecker::IsExitConditionSatisfied(std::ostream* os) {
  *os << "Waiting for a Nigori node with the proper passphrase type to become "
         "available on the server.";

  std::vector<sync_pb::SyncEntity> nigori_entities =
      fake_server()->GetPermanentSyncEntitiesByDataType(syncer::NIGORI);
  EXPECT_LE(nigori_entities.size(), 1U);
  return !nigori_entities.empty() &&
         syncer::ProtoPassphraseInt32ToEnum(
             nigori_entities[0].specifics().nigori().passphrase_type()) ==
             expected_passphrase_type_;
}

ServerCrossUserSharingPublicKeyChangedChecker::
    ServerCrossUserSharingPublicKeyChangedChecker(
        const std::string& previous_public_key)
    : previous_public_key_(previous_public_key) {}

bool ServerCrossUserSharingPublicKeyChangedChecker::IsExitConditionSatisfied(
    std::ostream* os) {
  *os << "Waiting for a Nigori node with a new cross-user sharing public key"
      << " available on the server";

  std::vector<sync_pb::SyncEntity> nigori_entities =
      fake_server()->GetPermanentSyncEntitiesByDataType(syncer::NIGORI);
  EXPECT_LE(nigori_entities.size(), 1U);
  return !nigori_entities.empty() &&
         nigori_entities[0]
                 .specifics()
                 .nigori()
                 .cross_user_sharing_public_key()
                 .x25519_public_key() != previous_public_key_;
}

ServerNigoriKeyNameChecker::ServerNigoriKeyNameChecker(
    const std::string& expected_key_name)
    : expected_key_name_(expected_key_name) {}

bool ServerNigoriKeyNameChecker::IsExitConditionSatisfied(std::ostream* os) {
  std::vector<sync_pb::SyncEntity> nigori_entities =
      fake_server()->GetPermanentSyncEntitiesByDataType(syncer::NIGORI);
  DCHECK_EQ(nigori_entities.size(), 1U);

  const std::string given_key_name =
      nigori_entities[0].specifics().nigori().encryption_keybag().key_name();

  *os << "Waiting for a Nigori node with proper key bag encryption key name ("
      << expected_key_name_ << ") to become available on the server."
      << "The server key bag encryption key name is " << given_key_name << ".";
  return given_key_name == expected_key_name_;
}

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

bool PassphraseRequiredChecker::IsExitConditionSatisfied(std::ostream* os) {
  *os << "Checking whether passhrase is required";
  return service()->IsEngineInitialized() &&
         service()->GetUserSettings()->IsPassphraseRequired();
}

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

bool KeystoreKeysRequiredChecker::IsExitConditionSatisfied(std::ostream* os) {
  *os << "Checking whether keystore keys are required";
  return service()->IsEngineInitialized() &&
         service()->GetUserSettings()->IsKeystoreKeyRequiredForTesting();
}

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

bool PassphraseAcceptedChecker::IsExitConditionSatisfied(std::ostream* os) {
  *os << "Checking whether passhrase is accepted";
  switch (service()->GetUserSettings()->GetPassphraseType().value_or(
      syncer::PassphraseType::kKeystorePassphrase)) {
    case syncer::PassphraseType::kKeystorePassphrase:
    case syncer::PassphraseType::kTrustedVaultPassphrase:
      return false;
    // With kImplicitPassphrase the user needs to enter the passphrase even
    // though it's not treated as an explicit passphrase.
    case syncer::PassphraseType::kImplicitPassphrase:
    case syncer::PassphraseType::kFrozenImplicitPassphrase:
    case syncer::PassphraseType::kCustomPassphrase:
      break;
  }
  return service()->IsEngineInitialized() &&
         !service()->GetUserSettings()->IsPassphraseRequired();
}

PassphraseTypeChecker::PassphraseTypeChecker(
    syncer::SyncServiceImpl* service,
    syncer::PassphraseType expected_passphrase_type)
    : SingleClientStatusChangeChecker(service),
      expected_passphrase_type_(expected_passphrase_type) {}

bool PassphraseTypeChecker::IsExitConditionSatisfied(std::ostream* os) {
  *os << "Checking expected passhrase type";
  return service()->GetUserSettings()->GetPassphraseType() ==
         expected_passphrase_type_;
}

TrustedVaultKeyRequiredStateChecker::TrustedVaultKeyRequiredStateChecker(
    syncer::SyncServiceImpl* service,
    bool desired_state)
    : SingleClientStatusChangeChecker(service), desired_state_(desired_state) {}

bool TrustedVaultKeyRequiredStateChecker::IsExitConditionSatisfied(
    std::ostream* os) {
  *os << "Waiting until trusted vault keys are " +
             std::string(desired_state_ ? "required" : "not required");
  return service()
             ->GetUserSettings()
             ->IsTrustedVaultKeyRequiredForPreferredDataTypes() ==
         desired_state_;
}

TrustedVaultKeysChangedStateChecker::TrustedVaultKeysChangedStateChecker(
    syncer::SyncServiceImpl* service)
    : service_(service) {
  service->GetSyncClientForTest()->GetTrustedVaultClient()->AddObserver(this);
}

TrustedVaultKeysChangedStateChecker::~TrustedVaultKeysChangedStateChecker() {
  service_->GetSyncClientForTest()->GetTrustedVaultClient()->RemoveObserver(
      this);
}

bool TrustedVaultKeysChangedStateChecker::IsExitConditionSatisfied(
    std::ostream* os) {
  *os << "Waiting for trusted vault keys change";
  return keys_changed_;
}

void TrustedVaultKeysChangedStateChecker::OnTrustedVaultKeysChanged(
    std::optional<trusted_vault::TrustedVaultUserActionTriggerForUMA> trigger) {
  keys_changed_ = true;
  CheckExitCondition();
}

void TrustedVaultKeysChangedStateChecker::
    OnTrustedVaultRecoverabilityChanged() {}

TrustedVaultRecoverabilityDegradedStateChecker::
    TrustedVaultRecoverabilityDegradedStateChecker(
        syncer::SyncServiceImpl* service,
        bool degraded)
    : SingleClientStatusChangeChecker(service), degraded_(degraded) {}

bool TrustedVaultRecoverabilityDegradedStateChecker::IsExitConditionSatisfied(
    std::ostream* os) {
  *os << "Waiting until trusted vault recoverability degraded state is "
      << degraded_;
  return service()->GetUserSettings()->IsTrustedVaultRecoverabilityDegraded() ==
         degraded_;
}
