910e62b5创建于 1月15日历史提交
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/host/setup/cloud_host_starter.h"

#include <memory>
#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/json/json_reader.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "remoting/base/cloud_service_client.h"
#include "remoting/base/compute_engine_service_client.h"
#include "remoting/base/http_status.h"
#include "remoting/base/instance_identity_token.h"
#include "remoting/base/logging.h"
#include "remoting/base/oauth_token_info.h"
#include "remoting/base/passthrough_oauth_token_getter.h"
#include "remoting/base/service_urls.h"
#include "remoting/host/host_config.h"
#include "remoting/host/setup/host_starter.h"
#include "remoting/host/setup/host_starter_base.h"
#include "remoting/proto/google/remoting/cloud/v1/provisioning_service.pb.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"

namespace remoting {

namespace {

using ProvisionGceInstanceResponse =
    ::google::remoting::cloud::v1::ProvisionGceInstanceResponse;

// A helper class which provisions a cloud machine for Chrome Remote Desktop.
class CloudHostStarter : public HostStarterBase {
 public:
  explicit CloudHostStarter(
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);

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

  ~CloudHostStarter() override;

  // ComputeEngineServiceClient callbacks.
  void OnApiAccessTokenRetrieved(const HttpStatus& status);
  void OnIdentityTokenRetrieved(const HttpStatus& status);

  // HostStarterBase implementation.
  void RetrieveApiAccessToken() override;
  void RegisterNewHost(std::optional<std::string> access_token) override;
  void RemoveOldHostFromDirectory(base::OnceClosure on_host_removed) override;
  void ApplyConfigValues(base::Value::Dict& config) override;

  // CloudServiceClient callback.
  void OnProvisionGceInstanceResponse(
      const HttpStatus& status,
      std::unique_ptr<ProvisionGceInstanceResponse> response);

 private:
  std::unique_ptr<CloudServiceClient> cloud_service_client_;
  std::unique_ptr<ComputeEngineServiceClient> compute_engine_service_client_;
  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;

  std::unique_ptr<PassthroughOAuthTokenGetter> api_access_token_getter_;

  std::optional<std::string> instance_identity_token_;

  SEQUENCE_CHECKER(sequence_checker_);

  base::WeakPtrFactory<CloudHostStarter> weak_ptr_factory_{this};
};

CloudHostStarter::CloudHostStarter(
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
    : HostStarterBase(url_loader_factory),
      compute_engine_service_client_(
          std::make_unique<ComputeEngineServiceClient>(url_loader_factory)),
      url_loader_factory_(url_loader_factory) {}

CloudHostStarter::~CloudHostStarter() = default;

void CloudHostStarter::RetrieveApiAccessToken() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Try to retrieve an Instance Identity Token before the access token. Making
  // this query first simplifies the logic a bit as we may end up skipping the
  // the access token request if an API_KEY is provided but we want to try to
  // get the identity token for both scenarios.
  compute_engine_service_client_->GetInstanceIdentityToken(
      base::StringPrintf(
          "https://%s",
          ServiceUrls::GetInstance()->remoting_cloud_public_endpoint()),
      base::BindOnce(&CloudHostStarter::OnIdentityTokenRetrieved,
                     weak_ptr_factory_.GetWeakPtr()));
}

void CloudHostStarter::OnIdentityTokenRetrieved(const HttpStatus& status) {
  if (status.ok()) {
    auto jwt = status.response_body();
    auto validated_token = InstanceIdentityToken::Create(jwt);
    if (validated_token.has_value()) {
      HOST_LOG << "Retrieved instance identity token:\n" << *validated_token;
      instance_identity_token_ = std::move(jwt);
    }
  } else {
    int error_code = static_cast<int>(status.error_code());
    LOG(WARNING) << "Failed to retrieve an Instance Identity token.\n"
                 << "  Error code: " << error_code << "\n"
                 << "  Message: " << status.error_message() << "\n"
                 << "  Body: " << status.response_body();
  }

  // The two modes to configure a Cloud host are to generate an API_KEY and use
  // that to access the provisioning RPC or to generate an access token using
  // the default service account. If an API_KEY is being used, we can skip the
  // access token request since it won't be used.
  if (params().api_key.empty()) {
    compute_engine_service_client_->GetServiceAccountAccessToken(
        base::BindOnce(&CloudHostStarter::OnApiAccessTokenRetrieved,
                       weak_ptr_factory_.GetWeakPtr()));
  } else {
    RegisterNewHost(/*access_token=*/std::nullopt);
  }
}

void CloudHostStarter::OnApiAccessTokenRetrieved(const HttpStatus& status) {
  if (!status.ok()) {
    HandleHttpStatusError(status);
    return;
  }
  if (status.response_body().empty()) {
    HandleError("Token response is empty.", Result::OAUTH_ERROR);
    return;
  }
  auto token_payload = base::JSONReader::Read(
      status.response_body(), base::JSON_PARSE_CHROMIUM_EXTENSIONS);
  if (!token_payload.has_value()) {
    HandleError("Token response was not valid JSON.", Result::OAUTH_ERROR);
    return;
  }
  auto* access_token = token_payload->GetDict().FindString("access_token");
  if (!access_token) {
    HandleError("Token response did not include an access token field.",
                Result::OAUTH_ERROR);
    return;
  }

  RegisterNewHost(*access_token);
}

void CloudHostStarter::RegisterNewHost(
    std::optional<std::string> access_token) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (access_token.has_value() && !access_token->empty()) {
    CHECK(params().api_key.empty());
    OAuthTokenInfo token_info{*access_token};
    api_access_token_getter_ =
        std::make_unique<PassthroughOAuthTokenGetter>(token_info);
    cloud_service_client_ =
        CloudServiceClient::CreateForGceDefaultServiceAccount(
            api_access_token_getter_.get(), url_loader_factory_);
  } else {
    CHECK(!params().api_key.empty());
    cloud_service_client_ = CloudServiceClient::CreateForGcpProject(
        params().api_key, url_loader_factory_);
  }

  cloud_service_client_->ProvisionGceInstance(
      params().owner_email, params().name, key_pair().GetPublicKey(),
      existing_host_id(), std::move(instance_identity_token_),
      base::BindOnce(&CloudHostStarter::OnProvisionGceInstanceResponse,
                     weak_ptr_factory_.GetWeakPtr()));
}

void CloudHostStarter::OnProvisionGceInstanceResponse(
    const HttpStatus& status,
    std::unique_ptr<ProvisionGceInstanceResponse> response) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!status.ok()) {
    HandleHttpStatusError(status);
    return;
  }

  OnNewHostRegistered(
      base::ToLowerASCII(response->directory_id()),
      /*owner_account_email=*/std::string(),
      base::ToLowerASCII(response->service_account_info().email()),
      response->service_account_info().authorization_code());
}

void CloudHostStarter::RemoveOldHostFromDirectory(
    base::OnceClosure on_host_removed) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // This workflow removes the existing host as part of the provisioning service
  // call so we don't need to make an additional service request here.
  std::move(on_host_removed).Run();
}

void CloudHostStarter::ApplyConfigValues(base::Value::Dict& config) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  config.Set(kHostTypeHintPath, kCloudHostTypeHint);
  config.Set(kRequireSessionAuthorizationPath, true);
}

}  // namespace

std::unique_ptr<HostStarter> ProvisionCloudInstance(
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {
  return std::make_unique<CloudHostStarter>(url_loader_factory);
}

}  // namespace remoting