chromium/chrome/browser/ash/policy/core/user_cloud_policy_token_forwarder.cc

// Copyright 2013 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/ash/policy/core/user_cloud_policy_token_forwarder.h"

#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/clock.h"
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h"
#include "components/policy/core/common/cloud/cloud_policy_core.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
#include "components/signin/public/identity_manager/scope_set.h"
#include "google_apis/gaia/gaia_constants.h"

namespace policy {

// static
const net::BackoffEntry::Policy
    UserCloudPolicyTokenForwarder::kFetchTokenRetryBackoffPolicy = {
        0,               // Number of initial errors to ignore.
        2 * 60 * 1000,   // Initial request delay in ms.
        2.0,             // Factor by which the waiting time will be multiplied.
        0.1,             // Fuzzing percentage.
        60 * 60 * 1000,  // Maximum request delay in ms.
        -1,              // Never discard the entry.
        true,  // Don't use initial delay unless last request was an error.
};

// static
constexpr char UserCloudPolicyTokenForwarder::kUMAChildUserOAuthTokenError[];

UserCloudPolicyTokenForwarder::UserCloudPolicyTokenForwarder(
    UserCloudPolicyManagerAsh* manager,
    signin::IdentityManager* identity_manager)
    : manager_(manager),
      identity_manager_(identity_manager),
      refresh_oauth_token_timer_(std::make_unique<base::RepeatingTimer>()),
      retry_backoff_(
          std::make_unique<net::BackoffEntry>(&kFetchTokenRetryBackoffPolicy)),
      clock_(base::DefaultClock::GetInstance()) {
  // Start by waiting for the CloudPolicyService to be initialized, so that
  // we can check if it already has a DMToken or not.
  if (manager_->core()->service()->IsInitializationComplete()) {
    StartRequest();
  } else {
    manager_->core()->service()->AddObserver(this);
  }
}

UserCloudPolicyTokenForwarder::~UserCloudPolicyTokenForwarder() {}

void UserCloudPolicyTokenForwarder::Shutdown() {
  access_token_fetcher_.reset();
  refresh_oauth_token_timer_.reset();
  manager_->core()->service()->RemoveObserver(this);
}

void UserCloudPolicyTokenForwarder::
    OnCloudPolicyServiceInitializationCompleted() {
  StartRequest();
}

bool UserCloudPolicyTokenForwarder::IsTokenFetchInProgressForTesting() const {
  return access_token_fetcher_.get() != nullptr;
}

bool UserCloudPolicyTokenForwarder::IsTokenRefreshScheduledForTesting() const {
  return refresh_oauth_token_timer_ && refresh_oauth_token_timer_->IsRunning();
}

std::optional<base::TimeDelta>
UserCloudPolicyTokenForwarder::GetTokenRefreshDelayForTesting() const {
  return IsTokenRefreshScheduledForTesting()
             ? refresh_oauth_token_timer_->GetCurrentDelay()
             : std::optional<base::TimeDelta>();
}

void UserCloudPolicyTokenForwarder::OverrideTimeForTesting(
    const base::Clock* clock,
    const base::TickClock* tick_clock,
    scoped_refptr<base::SequencedTaskRunner> task_runner) {
  clock_ = clock;
  refresh_oauth_token_timer_ =
      std::make_unique<base::RepeatingTimer>(tick_clock);
  refresh_oauth_token_timer_->SetTaskRunner(task_runner);
  retry_backoff_ = std::make_unique<net::BackoffEntry>(
      &kFetchTokenRetryBackoffPolicy, tick_clock);
}

void UserCloudPolicyTokenForwarder::StartRequest() {
  refresh_oauth_token_timer_->Stop();
  // TODO(mnissler): Once a better way to reconfirm whether a user is on the
  // login allowlist is available there is no reason to fetch the OAuth2 token
  // for regular user here if the client is already registered. If it is not
  // recurring token fetch for child user check and bail out here.
  signin::ScopeSet scopes;
  scopes.insert(GaiaConstants::kDeviceManagementServiceOAuth);
  scopes.insert(GaiaConstants::kGoogleUserInfoEmail);

  // NOTE: The primary account may not be available yet.
  access_token_fetcher_ =
      std::make_unique<signin::PrimaryAccountAccessTokenFetcher>(
          "policy_token_forwarder", identity_manager_, scopes,
          base::BindOnce(
              &UserCloudPolicyTokenForwarder::OnAccessTokenFetchCompleted,
              base::Unretained(this)),
          signin::PrimaryAccountAccessTokenFetcher::Mode::kWaitUntilAvailable,
          signin::ConsentLevel::kSignin);
}

void UserCloudPolicyTokenForwarder::OnAccessTokenFetchCompleted(
    GoogleServiceAuthError error,
    signin::AccessTokenInfo token_info) {
  DCHECK(access_token_fetcher_);

  if (error.state() == GoogleServiceAuthError::NONE) {
    oauth_token_ = std::move(token_info);
    manager_->OnAccessTokenAvailable(oauth_token_->token);
  } else {
    // This should seldom happen for initial policy fetch for regular user: if
    // the user is signing in for the first time then this was an online signin
    // and network errors are unlikely; if the user had already signed in before
    // then they should have policy cached, and StartRequest() wouldn't have
    // been invoked. Still, something just went wrong (server 500, or
    // something). Currently we don't recover in this case, and we'll just try
    // to register for policy again on the next signin.
    // TODO(joaodasilva, atwilson): consider blocking signin when this happens,
    // so that the user has to try again before getting into the session. That
    // would guarantee that a session always has fresh policy, or at least
    // enforces a cached policy.
    // In case of child user we keep refreshing the token and failed fetch will
    // be retried after some delay.
  }

  if (!manager_->RequiresOAuthTokenForChildUser()) {
    Shutdown();
    return;
  }

  UMA_HISTOGRAM_ENUMERATION(kUMAChildUserOAuthTokenError, error.state(),
                            GoogleServiceAuthError::NUM_STATES);

  // Schedule fetching fresh OAuth token after current token expiration, if
  // UserCloudPolicyManagerAsh needs valid OAuth token all the time.
  access_token_fetcher_.reset();
  // Retry after delay, if token request fails or the new token is expired.
  const base::Time now = clock_->Now();
  bool is_token_valid = oauth_token_ && oauth_token_->expiration_time > now;
  retry_backoff_->InformOfRequest(is_token_valid);
  base::TimeDelta time_to_next_refresh =
      is_token_valid ? oauth_token_->expiration_time - now
                     : retry_backoff_->GetTimeUntilRelease();
  VLOG(1) << "Next OAuth token refresh for DMServer auth in: "
          << time_to_next_refresh;
  refresh_oauth_token_timer_->Start(
      FROM_HERE, time_to_next_refresh,
      base::BindRepeating(&UserCloudPolicyTokenForwarder::StartRequest,
                          weak_ptr_factory_.GetWeakPtr()));
}

std::string_view UserCloudPolicyTokenForwarder::name() const {
  return "UserCloudPolicyTokenForwarder";
}

}  // namespace policy