// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/signin/internal/identity_manager/profile_oauth2_token_service.h"

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/signin/internal/identity_manager/oauth_multilogin_token_request.h"
#include "components/signin/internal/identity_manager/oauth_multilogin_token_response.h"
#include "components/signin/internal/identity_manager/profile_oauth2_token_service_delegate.h"
#include "components/signin/public/base/binding_key_registration_token_result.h"
#include "components/signin/public/base/device_id_helper.h"
#include "components/signin/public/base/signin_metrics.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "google_apis/gaia/oauth2_access_token_consumer.h"
#include "google_apis/gaia/oauth2_access_token_fetcher.h"
#include "google_apis/gaia/oauth2_access_token_manager.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"

#if BUILDFLAG(IS_IOS)
#include "components/signin/public/identity_manager/access_token_fetcher.h"
#endif

ProfileOAuth2TokenService::ProfileOAuth2TokenService(
    PrefService* user_prefs,
    std::unique_ptr<ProfileOAuth2TokenServiceDelegate> delegate)
    : user_prefs_(user_prefs),
      delegate_(std::move(delegate)),
      all_credentials_loaded_(false) {
  DCHECK(user_prefs_);
  DCHECK(delegate_);
  token_manager_ =
      std::make_unique<OAuth2AccessTokenManager>(/*delegate=*/this);
  // The `ProfileOAuth2TokenService` must be the first observer of `delegate_`.
  DCHECK(!delegate_->HasObserver());
  // `base::Unretained(this)` is safe as `this` owns `delegate_`.
  delegate_->SetOnRefreshTokenRevokedNotified(base::BindRepeating(
      &ProfileOAuth2TokenService::OnRefreshTokenRevokedNotified,
      base::Unretained(this)));
  token_service_observation_.Observe(delegate_.get());
  DCHECK(delegate_->HasObserver());
}

ProfileOAuth2TokenService::~ProfileOAuth2TokenService() {
  // Reset the observation before calling Shutdown(). Shutdown() may trigger
  // immediate delegate destruction, and the delegate's ObserverList destructor
  // checks that all observers have been removed (DUMP_WILL_BE_CHECK in
  // base/observer_list.h).
  token_service_observation_.Reset();
  token_manager_.reset();
  GetDelegate()->Shutdown();
}

std::unique_ptr<OAuth2AccessTokenFetcher>
ProfileOAuth2TokenService::CreateAccessTokenFetcher(
    const CoreAccountId& account_id,
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
    OAuth2AccessTokenConsumer* consumer,
    const std::string& token_binding_challenge) {
  return delegate_->CreateAccessTokenFetcher(account_id, url_loader_factory,
                                             consumer, token_binding_challenge);
}

void ProfileOAuth2TokenService::FixAccountErrorIfPossible() {
  delegate_->FixAccountErrorIfPossible();
}

scoped_refptr<network::SharedURLLoaderFactory>
ProfileOAuth2TokenService::GetURLLoaderFactory() const {
  return delegate_->GetURLLoaderFactory();
}

void ProfileOAuth2TokenService::OnAccessTokenInvalidated(
    const CoreAccountId& account_id,
    const std::string& client_id,
    const OAuth2AccessTokenManager::ScopeSet& scopes,
    const std::string& access_token) {
  delegate_->OnAccessTokenInvalidated(account_id, client_id, scopes,
                                      access_token);
}

void ProfileOAuth2TokenService::OnAccessTokenFetched(
    const CoreAccountId& account_id,
    const GoogleServiceAuthError& error) {
  // Update the auth error state so auth errors are appropriately communicated
  // to the user.
  delegate_->UpdateAuthError(account_id, error);
  if (error.IsPersistentError()) {
    // Needed for Enterprise on Windows to allow
    // `signin_util::ReauthWithCredentialProviderIfPossible()` to fix the
    // account.
    FixAccountErrorIfPossible();
  }
}

bool ProfileOAuth2TokenService::HasRefreshToken(
    const CoreAccountId& account_id) const {
  return RefreshTokenIsAvailable(account_id);
}

// static
void ProfileOAuth2TokenService::RegisterProfilePrefs(
    PrefRegistrySimple* registry) {
  registry->RegisterStringPref(prefs::kGoogleServicesSigninScopedDeviceId,
                               std::string());
}

ProfileOAuth2TokenServiceDelegate* ProfileOAuth2TokenService::GetDelegate() {
  return delegate_.get();
}

const ProfileOAuth2TokenServiceDelegate*
ProfileOAuth2TokenService::GetDelegate() const {
  return delegate_.get();
}

void ProfileOAuth2TokenService::AddObserver(
    ProfileOAuth2TokenServiceObserver* observer) {
  delegate_->AddObserver(observer);
}

void ProfileOAuth2TokenService::RemoveObserver(
    ProfileOAuth2TokenServiceObserver* observer) {
  delegate_->RemoveObserver(observer);
}

void ProfileOAuth2TokenService::AddAccessTokenDiagnosticsObserver(
    OAuth2AccessTokenManager::DiagnosticsObserver* observer) {
  token_manager_->AddDiagnosticsObserver(observer);
}

void ProfileOAuth2TokenService::RemoveAccessTokenDiagnosticsObserver(
    OAuth2AccessTokenManager::DiagnosticsObserver* observer) {
  token_manager_->RemoveDiagnosticsObserver(observer);
}

std::unique_ptr<OAuth2AccessTokenManager::Request>
ProfileOAuth2TokenService::StartRequest(
    const CoreAccountId& account_id,
    const OAuth2AccessTokenManager::ScopeSet& scopes,
    OAuth2AccessTokenManager::Consumer* consumer) {
  return token_manager_->StartRequest(account_id, scopes, consumer);
}

#if BUILDFLAG(IS_IOS)
void ProfileOAuth2TokenService::GetRefreshTokenFromDevice(
    const CoreAccountId& account_id,
    const OAuth2AccessTokenManager::ScopeSet& scopes,
    signin::AccessTokenFetcher::TokenCallback callback) {
  delegate_->GetRefreshTokenFromDevice(account_id, scopes, std::move(callback));
}
#endif

void ProfileOAuth2TokenService::StartRequestForMultilogin(
    signin::OAuthMultiloginTokenRequest& request,
    const std::string& token_binding_challenge,
    const std::string& ephemeral_public_key) {
  std::string refresh_token =
      delegate_->GetTokenForMultilogin(request.account_id());
  if (refresh_token.empty()) {
    // If we can't get refresh token from the delegate, start request for access
    // token.
    request.StartAccessTokenRequest(*token_manager_,
                                    {GaiaConstants::kOAuth1LoginScope});
    return;
  }

#if BUILDFLAG(ENABLE_DICE_SUPPORT)
  bool is_bound = delegate_->IsRefreshTokenBoundToKey(request.account_id());

  // Sign `token_binding_challenge` asynchronously if it's required.
  if (is_bound && !token_binding_challenge.empty()) {
    auto create_response_callback = base::BindOnce(
        [](std::string token, std::string assertion) {
          if (assertion.empty()) {
            // Even if the assertion failed, we want to make a server request
            // because the server doesn't verify assertions during dark launch.
            // TODO(crbug.com/377942773): fail here immediately after the
            // feature is fully launched.
            assertion = GaiaConstants::kTokenBindingAssertionFailedPlaceholder;
          }
          return signin::OAuthMultiloginTokenResponse(std::move(token),
                                                      std::move(assertion));
        },
        std::move(refresh_token));
    auto notify_request_callback =
        base::BindPostTaskToCurrentDefault(base::BindOnce(
            &signin::OAuthMultiloginTokenRequest::InvokeCallbackWithResult,
            request.AsWeakPtr()));
    delegate_->GenerateRefreshTokenBindingKeyAssertionForMultilogin(
        request.account_id(), token_binding_challenge, ephemeral_public_key,
        std::move(create_response_callback)
            .Then(std::move(notify_request_callback)));
    return;
  }

  signin::OAuthMultiloginTokenResponse response(
      std::move(refresh_token),
      is_bound ? std::string(GaiaConstants::kTokenBindingAssertionSentinel)
               : std::string());
#else
  signin::OAuthMultiloginTokenResponse response(std::move(refresh_token));
#endif  // BUILDFLAG(ENABLE_DICE_SUPPORT)

  // Create multilogin token response from the refresh token.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          &signin::OAuthMultiloginTokenRequest::InvokeCallbackWithResult,
          request.AsWeakPtr(), std::move(response)));
}

std::unique_ptr<OAuth2AccessTokenManager::Request>
ProfileOAuth2TokenService::StartRequestForClient(
    const CoreAccountId& account_id,
    const std::string& client_id,
    const std::string& client_secret,
    const OAuth2AccessTokenManager::ScopeSet& scopes,
    OAuth2AccessTokenManager::Consumer* consumer) {
  return token_manager_->StartRequestForClient(account_id, client_id,
                                               client_secret, scopes, consumer);
}

std::unique_ptr<OAuth2AccessTokenManager::Request>
ProfileOAuth2TokenService::StartRequestWithContext(
    const CoreAccountId& account_id,
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
    const OAuth2AccessTokenManager::ScopeSet& scopes,
    OAuth2AccessTokenManager::Consumer* consumer) {
  return token_manager_->StartRequestWithContext(account_id, url_loader_factory,
                                                 scopes, consumer);
}

void ProfileOAuth2TokenService::InvalidateAccessToken(
    const CoreAccountId& account_id,
    const OAuth2AccessTokenManager::ScopeSet& scopes,
    const std::string& access_token) {
  CHECK(!account_id.empty(), base::NotFatalUntil::M145);
  CHECK(!access_token.empty(), base::NotFatalUntil::M145);

  token_manager_->InvalidateAccessToken(account_id, scopes, access_token);
}

void ProfileOAuth2TokenService::InvalidateTokenForMultilogin(
    const CoreAccountId& failed_account,
    const std::string& token) {
  // Remove from cache. This will have no effect on desktop since token is a
  // refresh token and is not in cache.
  InvalidateAccessToken(failed_account, {GaiaConstants::kOAuth1LoginScope},
                        token);
  // For desktop refresh tokens can be invalidated directly in delegate. This
  // will have no effect on mobile.
  delegate_->InvalidateTokenForMultilogin(failed_account);
}

void ProfileOAuth2TokenService::SetRefreshTokenAvailableFromSourceCallback(
    RefreshTokenAvailableFromSourceCallback callback) {
  GetDelegate()->SetRefreshTokenAvailableFromSourceCallback(callback);
}

void ProfileOAuth2TokenService::SetRefreshTokenRevokedFromSourceCallback(
    RefreshTokenRevokedFromSourceCallback callback) {
  GetDelegate()->SetRefreshTokenRevokedFromSourceCallback(callback);
}

void ProfileOAuth2TokenService::LoadCredentials(
    const CoreAccountId& primary_account_id) {
  GetDelegate()->LoadCredentials(primary_account_id);
}

void ProfileOAuth2TokenService::UpdateCredentials(
    const CoreAccountId& account_id,
    const std::string& refresh_token,
    signin_metrics::SourceForRefreshTokenOperation source,
    const signin::TokenBindingInfo& token_binding_info) {
  GetDelegate()->UpdateCredentials(account_id, refresh_token, source,
                                   token_binding_info);
}

void ProfileOAuth2TokenService::RevokeCredentials(
    const CoreAccountId& account_id,
    signin_metrics::SourceForRefreshTokenOperation source) {
  GetDelegate()->RevokeCredentials(account_id, source);
}

void ProfileOAuth2TokenService::RevokeAllCredentials(
    signin_metrics::SourceForRefreshTokenOperation source) {
  GetDelegate()->RevokeAllCredentials(source);
}

const net::BackoffEntry* ProfileOAuth2TokenService::GetDelegateBackoffEntry() {
  return GetDelegate()->BackoffEntry();
}

#if BUILDFLAG(ENABLE_DICE_SUPPORT)
void ProfileOAuth2TokenService::ExtractCredentials(
    ProfileOAuth2TokenService* to_service,
    const CoreAccountId& account_id) {
  GetDelegate()->ExtractCredentials(to_service, account_id);
}
#endif

bool ProfileOAuth2TokenService::AreAllCredentialsLoaded() const {
  return all_credentials_loaded_;
}

std::vector<CoreAccountId> ProfileOAuth2TokenService::GetAccounts() const {
  if (!AreAllCredentialsLoaded()) {
    return std::vector<CoreAccountId>();
  }

  return GetDelegate()->GetAccounts();
}

#if BUILDFLAG(IS_IOS)
std::vector<AccountInfo> ProfileOAuth2TokenService::GetAccountsOnDevice()
    const {
  return GetDelegate()->GetAccountsOnDevice();
}
#endif  // BUILDFLAG(IS_IOS)

bool ProfileOAuth2TokenService::RefreshTokenIsAvailable(
    const CoreAccountId& account_id) const {
  return delegate_->RefreshTokenIsAvailable(account_id);
}

#if BUILDFLAG(IS_IOS)
bool ProfileOAuth2TokenService::RefreshTokenIsAvailableOnDevice(
    const CoreAccountId& account_id) const {
  return delegate_->RefreshTokenIsAvailableOnDevice(account_id);
}
#endif  // BUILDFLAG(IS_IOS)

GoogleServiceAuthError ProfileOAuth2TokenService::GetAuthError(
    const CoreAccountId& account_id) const {
  GoogleServiceAuthError error = delegate_->GetAuthError(account_id);
  DCHECK(!error.IsTransientError());
  return error;
}

void ProfileOAuth2TokenService::UpdateAuthErrorForTesting(
    const CoreAccountId& account_id,
    const GoogleServiceAuthError& error) {
  GetDelegate()->UpdateAuthError(account_id, error);
}

#if BUILDFLAG(ENABLE_DICE_SUPPORT)
bool ProfileOAuth2TokenService::GenerateBindingKeyRegistrationToken(
    base::span<const crypto::SignatureVerifier::SignatureAlgorithm>
        supported_algorithms,
    std::string_view auth_code,
    base::OnceCallback<void(
        std::optional<signin::BindingKeyRegistrationTokenResult>)> callback) {
  return delegate_->GenerateBindingKeyRegistrationToken(
      supported_algorithms, auth_code, std::move(callback));
}

std::vector<uint8_t> ProfileOAuth2TokenService::GetWrappedBindingKey(
    const CoreAccountId& account_id) const {
  return delegate_->GetWrappedBindingKey(account_id);
}

bool ProfileOAuth2TokenService::IsRefreshTokenBoundToMtls(
    const CoreAccountId& account_id) const {
  return delegate_->IsRefreshTokenBoundToMtls(account_id);
}

bool ProfileOAuth2TokenService::AllBoundTokensShareSameBindingKey() const {
  return delegate_->AllBoundTokensShareSameBindingKey();
}
#endif  // BUILDFLAG(ENABLE_DICE_SUPPORT)

void ProfileOAuth2TokenService::
    set_max_authorization_token_fetch_retries_for_testing(int max_retries) {
  token_manager_->set_max_authorization_token_fetch_retries_for_testing(
      max_retries);
}

void ProfileOAuth2TokenService::OverrideAccessTokenManagerForTesting(
    std::unique_ptr<OAuth2AccessTokenManager> token_manager) {
  token_manager_ = std::move(token_manager);
}

bool ProfileOAuth2TokenService::IsFakeProfileOAuth2TokenServiceForTesting()
    const {
  return false;
}

OAuth2AccessTokenManager* ProfileOAuth2TokenService::GetAccessTokenManager() {
  return token_manager_.get();
}

void ProfileOAuth2TokenService::OnRefreshTokenAvailable(
    const CoreAccountId& account_id) {
  token_manager_->CancelRequestsForAccount(
      account_id, GoogleServiceAuthError::CreateRequestCanceled());
  token_manager_->ClearCacheForAccount(account_id);
}

void ProfileOAuth2TokenService::OnRefreshTokenRevoked(
    const CoreAccountId& account_id) {
  // If this was the last token, recreate the device ID.
  RecreateDeviceIdIfNeeded();

  token_manager_->ClearCacheForAccount(account_id);
}

void ProfileOAuth2TokenService::OnRefreshTokenRevokedNotified(
    const CoreAccountId& account_id) {
  token_manager_->CancelRequestsForAccount(
      account_id, GoogleServiceAuthError::CreateAccountNotFound());
}

void ProfileOAuth2TokenService::OnRefreshTokensLoaded() {
  all_credentials_loaded_ = true;

  DCHECK_NE(signin::LoadCredentialsState::LOAD_CREDENTIALS_NOT_STARTED,
            GetDelegate()->load_credentials_state());
  DCHECK_NE(signin::LoadCredentialsState::LOAD_CREDENTIALS_IN_PROGRESS,
            GetDelegate()->load_credentials_state());

  // Ensure the device ID is not empty, and recreate it if all tokens were
  // cleared during the loading process.
  RecreateDeviceIdIfNeeded();
}

bool ProfileOAuth2TokenService::HasLoadCredentialsFinishedWithNoErrors() {
  switch (GetDelegate()->load_credentials_state()) {
    case signin::LoadCredentialsState::LOAD_CREDENTIALS_NOT_STARTED:
    case signin::LoadCredentialsState::LOAD_CREDENTIALS_IN_PROGRESS:
      // LoadCredentials has not finished.
      return false;
    case signin::LoadCredentialsState::
        LOAD_CREDENTIALS_FINISHED_WITH_DB_CANNOT_BE_OPENED:
    case signin::LoadCredentialsState::LOAD_CREDENTIALS_FINISHED_WITH_DB_ERRORS:
    case signin::LoadCredentialsState::
        LOAD_CREDENTIALS_FINISHED_WITH_DECRYPT_ERRORS:
    case signin::LoadCredentialsState::
        LOAD_CREDENTIALS_FINISHED_WITH_UNKNOWN_ERRORS:
      // LoadCredentials finished, but with errors
      return false;
    case signin::LoadCredentialsState::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS:
    case signin::LoadCredentialsState::
        LOAD_CREDENTIALS_FINISHED_WITH_NO_TOKEN_FOR_PRIMARY_ACCOUNT:
      // Load credentials finished with success.
      return true;
  }
}

void ProfileOAuth2TokenService::RecreateDeviceIdIfNeeded() {
// On ChromeOS the device ID is not managed by the token service.
#if !BUILDFLAG(IS_CHROMEOS)
  if (AreAllCredentialsLoaded() && HasLoadCredentialsFinishedWithNoErrors() &&
      GetAccounts().empty()) {
    signin::RecreateSigninScopedDeviceId(user_prefs_);
  }
#endif
}
