chromium/chromeos/ash/components/boca/babelorca/oauth_token_fetcher.cc

// 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 "chromeos/ash/components/boca/babelorca/oauth_token_fetcher.h"

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

#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_forward.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "chromeos/ash/components/boca/babelorca/token_data_wrapper.h"
#include "chromeos/ash/components/boca/babelorca/token_fetcher.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/access_token_fetcher.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/google_service_auth_error.h"

namespace ash::babelorca {
namespace {

bool IsOAuthTokenFetchRetryableError(
    GoogleServiceAuthError::State error_state) {
  return error_state == GoogleServiceAuthError::CONNECTION_FAILED ||
         error_state == GoogleServiceAuthError::SERVICE_UNAVAILABLE;
}

}  // namespace

OAuthTokenFetcher::OAuthTokenFetcher(signin::IdentityManager* identity_manager)
    : identity_manager_(identity_manager) {}

OAuthTokenFetcher::~OAuthTokenFetcher() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

// TokenFetcher:
void OAuthTokenFetcher::FetchToken(TokenFetchCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (access_token_fetcher_) {
    LOG(ERROR) << "Tachyon oauth token fetch is already in progress.";
    std::move(callback).Run(std::nullopt);
    return;
  }
  FetchTokenInternal(std::move(callback), /*retry_num=*/0);
}

void OAuthTokenFetcher::FetchTokenInternal(TokenFetchCallback callback,
                                           int retry_num) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(identity_manager_);
  static constexpr char kOauthConsumerName[] = "babel_orca";
  access_token_fetcher_ = identity_manager_->CreateAccessTokenFetcherForAccount(
      identity_manager_->GetPrimaryAccountId(signin::ConsentLevel::kSignin),
      kOauthConsumerName, {GaiaConstants::kTachyonOAuthScope},
      base::BindOnce(
          &OAuthTokenFetcher::OnOAuthTokenRequestCompleted,
          // base::Unretained is safe, `this` owns `access_token_fetcher_`.
          base::Unretained(this), std::move(callback), retry_num),
      signin::AccessTokenFetcher::Mode::kWaitUntilRefreshTokenAvailable);
}

void OAuthTokenFetcher::OnOAuthTokenRequestCompleted(
    TokenFetchCallback callback,
    int retry_num,
    GoogleServiceAuthError error,
    signin::AccessTokenInfo access_token_info) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  static constexpr int kMaxRetries = 2;
  static constexpr base::TimeDelta kRetryInitialBackoff =
      base::Milliseconds(500);
  if (retry_num < kMaxRetries &&
      IsOAuthTokenFetchRetryableError(error.state())) {
    retry_timer_.Start(FROM_HERE, (retry_num + 1) * kRetryInitialBackoff,
                       base::BindOnce(&OAuthTokenFetcher::FetchTokenInternal,
                                      base::Unretained(this),
                                      std::move(callback), retry_num + 1));
    return;
  }
  // Reset `access_token_fetcher_` to allow new fetch token requests.
  access_token_fetcher_.reset();
  if (error.state() == GoogleServiceAuthError::NONE) {
    std::move(callback).Run(TokenDataWrapper(
        std::move(access_token_info.token), access_token_info.expiration_time));
    return;
  }
  std::move(callback).Run(std::nullopt);
}

}  // namespace ash::babelorca