chromium/chrome/browser/device_identity/chromeos/device_oauth2_token_store_chromeos.cc

// Copyright 2020 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/device_identity/chromeos/device_oauth2_token_store_chromeos.h"

#include <optional>
#include <utility>

#include "base/logging.h"
#include "chrome/browser/ash/settings/token_encryptor.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/cryptohome/system_salt_getter.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"

namespace chromeos {

DeviceOAuth2TokenStoreChromeOS::DeviceOAuth2TokenStoreChromeOS(
    PrefService* local_state)
    : local_state_(local_state),
      service_account_identity_subscription_(
          ash::CrosSettings::Get()->AddSettingsObserver(
              ash::kServiceAccountIdentity,
              base::BindRepeating(&DeviceOAuth2TokenStoreChromeOS::
                                      OnServiceAccountIdentityChanged,
                                  base::Unretained(this)))) {}

DeviceOAuth2TokenStoreChromeOS::~DeviceOAuth2TokenStoreChromeOS() {
  FlushTokenSaveCallbacks(false);
}

// static
void DeviceOAuth2TokenStoreChromeOS::RegisterPrefs(
    PrefRegistrySimple* registry) {
  registry->RegisterStringPref(prefs::kDeviceRobotAnyApiRefreshTokenV1,
                               std::string());
  registry->RegisterStringPref(prefs::kDeviceRobotAnyApiRefreshTokenV2,
                               std::string());
}

void DeviceOAuth2TokenStoreChromeOS::Init(InitCallback callback) {
  state_ = State::INITIALIZING;
  // Pull in the system salt.
  ash::SystemSaltGetter::Get()->GetSystemSalt(
      base::BindOnce(&DeviceOAuth2TokenStoreChromeOS::DidGetSystemSalt,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

CoreAccountId DeviceOAuth2TokenStoreChromeOS::GetAccountId() const {
  std::string email;
  ash::CrosSettings::Get()->GetString(ash::kServiceAccountIdentity, &email);
  return CoreAccountId::FromEmail(email);
}

std::string DeviceOAuth2TokenStoreChromeOS::GetRefreshToken() const {
  return refresh_token_;
}

void DeviceOAuth2TokenStoreChromeOS::SetAndSaveRefreshToken(
    const std::string& refresh_token,
    StatusCallback callback) {
  refresh_token_ = refresh_token;
  // If the robot account ID is not available yet, do not announce the token. It
  // will be done from OnServiceAccountIdentityChanged() once the robot account
  // ID becomes available as well.
  if (observer() && !GetAccountId().empty()) {
    observer()->OnRefreshTokenAvailable();
  }

  token_save_callbacks_.push_back(std::move(callback));
  if (state_ == State::READY) {
    if (system_salt_.empty()) {
      FlushTokenSaveCallbacks(false);
    } else {
      EncryptAndSaveToken();
    }
  }
}

void DeviceOAuth2TokenStoreChromeOS::PrepareTrustedAccountId(
    TrustedAccountIdCallback callback) {
  // Make sure the value returned by GetRobotAccountId has been validated
  // against current device settings.
  switch (ash::CrosSettings::Get()->PrepareTrustedValues(
      base::BindOnce(&DeviceOAuth2TokenStoreChromeOS::PrepareTrustedAccountId,
                     weak_ptr_factory_.GetWeakPtr(), callback))) {
    case ash::CrosSettingsProvider::TRUSTED:
      // All good, let the service compare account ids.
      callback.Run(true);
      return;
    case ash::CrosSettingsProvider::TEMPORARILY_UNTRUSTED:
      // The callback passed to PrepareTrustedValues above will trigger a
      // re-check eventually.
      return;
    case ash::CrosSettingsProvider::PERMANENTLY_UNTRUSTED:
      // There's no trusted account id, which is equivalent to no token present.
      LOG(WARNING) << "Device settings permanently untrusted.";
      callback.Run(false);
      return;
  }
}

void DeviceOAuth2TokenStoreChromeOS::FlushTokenSaveCallbacks(bool result) {
  std::vector<DeviceOAuth2TokenStore::StatusCallback> callbacks;
  callbacks.swap(token_save_callbacks_);
  for (std::vector<DeviceOAuth2TokenStore::StatusCallback>::iterator callback(
           callbacks.begin());
       callback != callbacks.end(); ++callback) {
    if (!callback->is_null()) {
      std::move(*callback).Run(result);
    }
  }
}

void DeviceOAuth2TokenStoreChromeOS::EncryptAndSaveToken() {
  ash::CryptohomeTokenEncryptor encryptor(system_salt_);
  std::string encrypted_refresh_token =
      encryptor.EncryptWithSystemSalt(refresh_token_);
  bool result = true;
  if (encrypted_refresh_token.empty()) {
    LOG(ERROR) << "Failed to encrypt refresh token; save aborted.";
    result = false;
  } else {
    local_state_->SetString(prefs::kDeviceRobotAnyApiRefreshTokenV2,
                            encrypted_refresh_token);
  }

  FlushTokenSaveCallbacks(result);
}

std::optional<std::string>
DeviceOAuth2TokenStoreChromeOS::LoadAndDecryptToken() {
  // Try to load a more strongly encrypted v2 token if it exists, but if it does
  // not it will fall back to trying to load a weaker v1 token. If neither
  // exists we return an empty string.
  ash::CryptohomeTokenEncryptor encryptor(system_salt_);
  std::string encrypted_token, decrypted_token;
  if (encrypted_token =
          local_state_->GetString(prefs::kDeviceRobotAnyApiRefreshTokenV2);
      !encrypted_token.empty()) {
    decrypted_token = encryptor.DecryptWithSystemSalt(encrypted_token);
    if (decrypted_token.empty()) {
      LOG(ERROR) << "Failed to decrypt v2 refresh token.";
      return std::nullopt;
    }
  } else if (encrypted_token = local_state_->GetString(
                 prefs::kDeviceRobotAnyApiRefreshTokenV1);
             !encrypted_token.empty()) {
    decrypted_token = encryptor.WeakDecryptWithSystemSalt(encrypted_token);
    if (decrypted_token.empty()) {
      LOG(ERROR) << "Failed to decrypt v1 refresh token.";
      return std::nullopt;
    }
  }
  return decrypted_token;
}

void DeviceOAuth2TokenStoreChromeOS::DidGetSystemSalt(
    InitCallback callback,
    const std::string& system_salt) {
  state_ = State::READY;
  system_salt_ = system_salt;

  // Bail out if system salt is not available.
  if (system_salt_.empty()) {
    LOG(ERROR) << "Failed to get system salt.";
    FlushTokenSaveCallbacks(false);
    std::move(callback).Run(false, false);
    return;
  }

  // If the token has been set meanwhile, write it to |local_state_|.
  if (!refresh_token_.empty()) {
    EncryptAndSaveToken();
    std::move(callback).Run(true, false);
    return;
  }

  // Otherwise, load the refresh token from |local_state_|.
  std::optional<std::string> token = LoadAndDecryptToken();
  if (token.has_value()) {
    refresh_token_ = std::move(*token);
    std::move(callback).Run(true, true);
    return;
  }
  std::move(callback).Run(false, false);
}

void DeviceOAuth2TokenStoreChromeOS::OnServiceAccountIdentityChanged() {
  if (observer() && !GetAccountId().empty() && !refresh_token_.empty()) {
    observer()->OnRefreshTokenAvailable();
  }
}

}  // namespace chromeos