910e62b5创建于 1月15日历史提交
// Copyright 2019 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/test/test_token_storage.h"

#include <optional>

#include "base/files/file_util.h"
#include "base/files/important_file_writer.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/values.h"

namespace {
const base::FilePath::CharType kTokenFileName[] =
    FILE_PATH_LITERAL("tokens.json");
const base::FilePath::CharType kRemotingFolder[] =
    FILE_PATH_LITERAL("remoting");
const base::FilePath::CharType kTokenStoreFolder[] =
    FILE_PATH_LITERAL("token_store");
constexpr char kUnspecifiedUsername[] = "unspecified";
constexpr char kRefreshTokenKey[] = "refresh_token";
constexpr char kUserEmailKey[] = "user_email";
constexpr char kAccessTokenKey[] = "access_token";
constexpr char kScopesKey[] = "scopes";
constexpr char kDeviceIdKey[] = "device_id";
}  // namespace

namespace remoting {
namespace test {

// Provides functionality to write a refresh token to a local folder on disk and
// read it back during subsequent tool runs.
class TestTokenStorageOnDisk : public TestTokenStorage {
 public:
  TestTokenStorageOnDisk(const std::string& user_name,
                         const base::FilePath& tokens_file_path);

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

  ~TestTokenStorageOnDisk() override;

  // TestTokenStorage interface.
  std::string FetchRefreshToken() override;
  bool StoreRefreshToken(const std::string& refresh_token) override;
  std::string FetchUserEmail() override;
  bool StoreUserEmail(const std::string& user_email) override;
  std::string FetchAccessToken() override;
  bool StoreAccessToken(const std::string& access_token) override;
  std::string FetchScopes() override;
  bool StoreScopes(const std::string& scopes) override;
  std::string FetchDeviceId() override;
  bool StoreDeviceId(const std::string& device_id) override;

 private:
  std::string FetchTokenFromKey(const std::string& key);
  bool StoreTokenForKey(const std::string& key, const std::string& value);

  // Returns the path for the file used to read from or store a token for the
  // user.
  base::FilePath GetPathForTokens();

  // Used to access the user specific token file.
  std::string user_name_;

  // Path used to retrieve the tokens file.
  base::FilePath file_path_;
};

TestTokenStorageOnDisk::TestTokenStorageOnDisk(const std::string& user_name,
                                               const base::FilePath& file_path)
    : user_name_(user_name), file_path_(base::MakeAbsoluteFilePath(file_path)) {
  if (user_name_.empty()) {
    user_name_ = kUnspecifiedUsername;
  }
  VLOG(0) << "User name: " << user_name_;
  VLOG(0) << "Storage file path: " << GetPathForTokens();
}

TestTokenStorageOnDisk::~TestTokenStorageOnDisk() = default;

std::string TestTokenStorageOnDisk::FetchRefreshToken() {
  return FetchTokenFromKey(kRefreshTokenKey);
}

bool TestTokenStorageOnDisk::StoreRefreshToken(
    const std::string& refresh_token) {
  return StoreTokenForKey(kRefreshTokenKey, refresh_token);
}

std::string TestTokenStorageOnDisk::FetchUserEmail() {
  return FetchTokenFromKey(kUserEmailKey);
}

bool TestTokenStorageOnDisk::StoreUserEmail(const std::string& user_email) {
  return StoreTokenForKey(kUserEmailKey, user_email);
}

std::string TestTokenStorageOnDisk::FetchAccessToken() {
  return FetchTokenFromKey(kAccessTokenKey);
}

bool TestTokenStorageOnDisk::StoreAccessToken(const std::string& access_token) {
  return StoreTokenForKey(kAccessTokenKey, access_token);
}

std::string TestTokenStorageOnDisk::FetchScopes() {
  return FetchTokenFromKey(kScopesKey);
}

bool TestTokenStorageOnDisk::StoreScopes(const std::string& scopes) {
  return StoreTokenForKey(kScopesKey, scopes);
}

std::string TestTokenStorageOnDisk::FetchDeviceId() {
  return FetchTokenFromKey(kDeviceIdKey);
}

bool TestTokenStorageOnDisk::StoreDeviceId(const std::string& device_id) {
  return StoreTokenForKey(kDeviceIdKey, device_id);
}

std::string TestTokenStorageOnDisk::FetchTokenFromKey(const std::string& key) {
  base::FilePath file_path(GetPathForTokens());
  DCHECK(!file_path.empty());
  VLOG(1) << "Reading string from: " << file_path.value();

  std::string file_contents;
  if (!base::ReadFileToString(file_path, &file_contents)) {
    VLOG(1) << "Couldn't read file: " << file_path.value();
    return std::string();
  }

  std::optional<base::Value::Dict> token_data = base::JSONReader::ReadDict(
      file_contents, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
  if (!token_data) {
    LOG(ERROR) << "File contents were not valid JSON, "
               << "could not retrieve token.";
    return std::string();
  }

  const std::string* token =
      token_data->FindStringByDottedPath(user_name_ + '.' + key);
  if (!token) {
    VLOG(1) << "Could not find token for: " << key;
    return std::string();
  }

  return *token;
}

bool TestTokenStorageOnDisk::StoreTokenForKey(const std::string& key,
                                              const std::string& value) {
  DCHECK(!value.empty());

  base::FilePath file_path(GetPathForTokens());
  DCHECK(!file_path.empty());
  VLOG(2) << "Storing token to: " << file_path.value();

  base::FilePath file_dir(file_path.DirName());
  if (!base::DirectoryExists(file_dir) && !base::CreateDirectory(file_dir)) {
    LOG(ERROR) << "Failed to create directory, token not stored.";
    return false;
  }

  std::string file_contents("{}");
  if (base::PathExists(file_path)) {
    if (!base::ReadFileToString(file_path, &file_contents)) {
      LOG(ERROR) << "Invalid token file: " << file_path.value();
      return false;
    }
  }

  std::optional<base::Value::Dict> token_data = base::JSONReader::ReadDict(
      file_contents, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
  if (!token_data) {
    LOG(ERROR) << "Invalid token file format, could not store token.";
    return false;
  }

  token_data->SetByDottedPath(user_name_ + '.' + key, value);
  std::optional<std::string> json_string = base::WriteJson(*token_data);
  if (!json_string.has_value()) {
    LOG(ERROR) << "Couldn't convert JSON data to string";
    return false;
  }

  if (!base::ImportantFileWriter::WriteFileAtomically(file_path,
                                                      json_string.value())) {
    LOG(ERROR) << "Failed to save token to the file on disk.";
    return false;
  }

  return true;
}

base::FilePath TestTokenStorageOnDisk::GetPathForTokens() {
  base::FilePath file_path(file_path_);

  // If we weren't given a specific file path, then use the default path.
  if (file_path_.empty()) {
    if (!GetTempDir(&file_path)) {
      LOG(WARNING) << "Failed to retrieve temporary directory path.";
      return base::FilePath();
    }

    file_path = file_path.Append(kRemotingFolder);
    file_path = file_path.Append(kTokenStoreFolder);
  }

  // If no file has been specified, then we will use a default file name.
  if (file_path.Extension().empty()) {
    file_path = file_path.Append(kTokenFileName);
  }

  return file_path;
}

std::unique_ptr<TestTokenStorage> TestTokenStorage::OnDisk(
    const std::string& user_name,
    const base::FilePath& refresh_token_file_path) {
  return std::make_unique<TestTokenStorageOnDisk>(user_name,
                                                  refresh_token_file_path);
}

}  // namespace test
}  // namespace remoting