chromium/components/account_manager_core/chromeos/account_manager.h

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

#ifndef COMPONENTS_ACCOUNT_MANAGER_CORE_CHROMEOS_ACCOUNT_MANAGER_H_
#define COMPONENTS_ACCOUNT_MANAGER_CORE_CHROMEOS_ACCOUNT_MANAGER_H_

#include <map>
#include <memory>
#include <optional>
#include <ostream>
#include <string>
#include <utility>
#include <vector>

#include "base/component_export.h"
#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/sequence_checker.h"
#include "components/account_manager_core/account.h"

class OAuth2AccessTokenFetcher;
class OAuth2AccessTokenConsumer;
class PrefRegistrySimple;
class PrefService;

namespace base {
class SequencedTaskRunner;
class ImportantFileWriter;
class TimeTicks;
}  // namespace base

namespace network {
class SharedURLLoaderFactory;
}

namespace account_manager {

class COMPONENT_EXPORT(ACCOUNT_MANAGER_CORE) AccountManager {
 public:
  // A dummy token stored against Active Directory accounts in Account Manager.
  // Accounts stored in Account Manager must have a token associated with them.
  // Active Directory accounts use Kerberos tickets for authentication, which is
  // handled by a different infrastructure. Hence, we need a dummy token to
  // store Active Directory accounts in Account Manager.
  // See |AccountManager::UpsertToken|.
  static const char kActiveDirectoryDummyToken[];

  // A special token that is guaranteed to cheaply fail all network requests
  // performed using it.
  // Note that it neither marks an account in Account Manager as invalid, nor
  // removes the account. This is useful in scenarios where account names are
  // imported from elsewhere (Chrome content area or ARC++) and their tokens are
  // not yet known, but at the same time, these accounts need to be surfaced on
  // the UI.
  // Do not use this token for Active Directory accounts,
  // |kActiveDirectoryDummyToken| is meant for that.
  // See |AccountManager::UpsertToken|.
  static const char* const kInvalidToken;

  // Callback used for the (asynchronous) GetAccounts() call.
  using AccountListCallback =
      base::OnceCallback<void(const std::vector<::account_manager::Account>&)>;

  using DelayNetworkCallRunner =
      base::RepeatingCallback<void(base::OnceClosure)>;

  class Observer {
   public:
    Observer();
    Observer(const Observer&) = delete;
    Observer& operator=(const Observer&) = delete;
    virtual ~Observer();

    // Called when the token for |account| is updated/inserted.
    // Use |AccountManager::AddObserver| to add an |Observer|.
    // Note: |Observer|s which register with |AccountManager| before its
    // initialization is complete will get notified when |AccountManager| is
    // fully initialized.
    // Note: |Observer|s which register with |AccountManager| after its
    // initialization is complete will not get an immediate
    // notification-on-registration.
    virtual void OnTokenUpserted(const ::account_manager::Account& account) = 0;

    // Called when an account has been removed from AccountManager.
    // Observers that may have cached access tokens (fetched via
    // |AccountManager::CreateAccessTokenFetcher|), must clear their cache entry
    // for this |account| on receiving this callback.
    virtual void OnAccountRemoved(
        const ::account_manager::Account& account) = 0;
  };

  // Note: |Initialize| MUST be called at least once on this object.
  AccountManager();
  AccountManager(const AccountManager&) = delete;
  AccountManager& operator=(const AccountManager&) = delete;
  virtual ~AccountManager();

  static void RegisterPrefs(PrefRegistrySimple* registry);

  // Sets a |PrefService|. Account Manager will use this instance to read its
  // policies.
  void SetPrefService(PrefService* pref_service);

  // |home_dir| is the path of the Device Account's home directory (root of the
  // user's cryptohome). If |home_dir| is |base::FilePath::empty()|, then |this|
  // |AccountManager| does not persist any data to disk.
  // |request_context| is a non-owning pointer.
  // |delay_network_call_runner| is basically a wrapper for
  // |ash::DelayNetworkCall|. Cannot use |ash::DelayNetworkCall| due
  // to linking/dependency constraints.
  // This method MUST be called at least once in the lifetime of AccountManager.
  void Initialize(
      const base::FilePath& home_dir,
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
      DelayNetworkCallRunner delay_network_call_runner);

  // Same as above except that it accepts an |initialization_callback|, which
  // will be called after Account Manager has been fully initialized.
  // If Account Manager has already been fully initialized,
  // |initialization_callback| is called immediately.
  // Note: During initialization, there is no ordering guarantee between
  // |initialization_callback| and Account Manager's observers getting their
  // callbacks.
  void Initialize(
      const base::FilePath& home_dir,
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
      DelayNetworkCallRunner delay_network_call_runner,
      base::OnceClosure initialization_callback);

  // Initializes |AccountManager| for ephemeral / in-memory usage.
  // Useful for tests that cannot afford to write to disk and clean up after
  // themselves.
  void InitializeInEphemeralMode(
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);

  // Same as above, except it allows clients to provide a callback which will be
  // called after initialization.
  void InitializeInEphemeralMode(
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
      base::OnceClosure initialization_callback);

  // Returns |true| if |AccountManager| has been fully initialized.
  bool IsInitialized() const;

  // Gets (async) a list of account keys known to `AccountManager`. Note that
  // `callback` will be immediately called in the same thread if
  // `AccountManager` has been fully initialized and hence it may not be safe to
  // call this method directly in some class's constructor, with a callback on
  // the same class, since it may result in a method call on a partially
  // constructed object.
  void GetAccounts(AccountListCallback callback);

  // Gets (async) the raw, un-canonicalized email id corresponding to
  // `account_key`. `callback` is called with an empty string if `account_key`
  // is not known to Account Manager.
  void GetAccountEmail(const ::account_manager::AccountKey& account_key,
                       base::OnceCallback<void(const std::string&)> callback);

  // Removes an account. Does not do anything if `account_key` is not known by
  // `AccountManager`.
  // Observers are notified about an account removal through
  // `Observer::OnAccountRemoved`.
  // If the account being removed is a GAIA account, a token revocation with
  // GAIA is also attempted, on a best effort basis. Even if token revocation
  // with GAIA fails, AccountManager will forget the account.
  void RemoveAccount(const ::account_manager::AccountKey& account_key);

  // Updates or inserts an account. `raw_email` is the raw, un-canonicalized
  // email id for `account_key`. `raw_email` must not be empty. Use
  // `AccountManager::kActiveDirectoryDummyToken` as the `token` for Active
  // Directory accounts, and `AccountManager::kInvalidToken` for Gaia accounts
  // with unknown tokens.
  // Note: This API is idempotent.
  void UpsertAccount(const ::account_manager::AccountKey& account_key,
                     const std::string& raw_email,
                     const std::string& token);

  // Updates the token for the account corresponding to the given `account_key`.
  // The account must be known to Account Manager. See `UpsertAccount` for
  // information about adding an account.
  // Note: This API is idempotent.
  void UpdateToken(const ::account_manager::AccountKey& account_key,
                   const std::string& token);

  // Add a non owning pointer to an |AccountManager::Observer|.
  void AddObserver(Observer* observer);

  // Removes an |AccountManager::Observer|. Does nothing if the |observer| is
  // not in the list of known observers.
  void RemoveObserver(Observer* observer);

  // Sets the provided URL Loader Factory. Used only by tests.
  void SetUrlLoaderFactoryForTests(
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);

  // Creates and returns an `OAuth2AccessTokenFetcher` using the refresh token
  // stored for `account_key`.
  // Virtual for testing.
  virtual std::unique_ptr<OAuth2AccessTokenFetcher> CreateAccessTokenFetcher(
      const ::account_manager::AccountKey& account_key,
      OAuth2AccessTokenConsumer* consumer);

  // Returns |true| if an LST is available for |account_key|. Note that
  // "availability" does not guarantee "validity", i.e. this method will return
  // true for LSTs that have expired / been invalidated.
  // Note: Always returns false for Active Directory accounts.
  // Note: This method will return |false| if |AccountManager| has not been
  // initialized yet.
  bool IsTokenAvailable(const ::account_manager::AccountKey& account_key) const;

  // Calls the `callback` with true if the token stored against `account_key` is
  // a dummy Gaia token.
  void HasDummyGaiaToken(const ::account_manager::AccountKey& account_key,
                         base::OnceCallback<void(bool)> callback);

  // Calls the `callback` with a list of pairs of `account_key` and boolean
  // which is set to true if the token stored against `account_key` is a dummy
  // Gaia token, for all accounts stored in AccountManager. See
  // `HasDummyGaiaToken`.
  void CheckDummyGaiaTokenForAllAccounts(
      base::OnceCallback<
          void(const std::vector<std::pair<::account_manager::Account, bool>>&)>
          callback);

  // Returns the Base16 encoded SHA1 hash for the token stored against
  // `account_key`. This hash is meant only for debugging purposes and is not
  // meant be used as a cryptographically secure hash. Do not persist this
  // outside the user's cryptohome.
  // Returns an empty string if `account_key` is not available in
  // `AccountManager`.
  // TODO(http://b/297484217): Remove this API.
  void GetTokenHash(const ::account_manager::AccountKey& account_key,
                    base::OnceCallback<void(const std::string&)> callback);

  base::WeakPtr<AccountManager> GetWeakPtr();

 private:
  enum InitializationState {
    kNotStarted,   // Initialize has not been called
    kInProgress,   // Initialize has been called but not completed
    kInitialized,  // Initialization was successfully completed
  };

  // Account Manager's internal information about an account.
  struct AccountInfo {
    std::string raw_email;
    std::string token;
  };

  // A util class to revoke Gaia tokens on server. This class is meant to be
  // used for a single request.
  class GaiaTokenRevocationRequest;

  // A util class to fetch access tokens.
  class AccessTokenFetcher;

  friend class AccountManagerTest;
  FRIEND_TEST_ALL_PREFIXES(AccountManagerTest, TestInitializationCompletes);
  FRIEND_TEST_ALL_PREFIXES(AccountManagerTest, TestTokenPersistence);
  FRIEND_TEST_ALL_PREFIXES(AccountManagerTest,
                           UpdatingAccountEmailShouldNotOverwriteTokens);
  FRIEND_TEST_ALL_PREFIXES(AccountManagerTest,
                           UpdatingTokensShouldNotOverwriteAccountEmail);

  using AccountMap = std::map<::account_manager::AccountKey, AccountInfo>;

  // Same as the public |Initialize| except for a |task_runner|.
  void Initialize(
      const base::FilePath& home_dir,
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
      DelayNetworkCallRunner delay_network_call_runner,
      scoped_refptr<base::SequencedTaskRunner> task_runner,
      base::OnceClosure initialization_callback);

  // Loads accounts from disk and returns the result.
  static AccountMap LoadAccountsFromDisk(
      const base::FilePath& tokens_file_path);

  // Reads accounts from |accounts| and inserts them in |accounts_| and runs all
  // callbacks waiting on |AccountManager| initialization.
  // |initialization_start_time| is the time at which
  // |AccountManager::Initialize| was called.
  void InsertAccountsAndRunInitializationCallbacks(
      const base::TimeTicks& initialization_start_time,
      const AccountMap& accounts);

  // Accepts a closure and runs it immediately if |AccountManager| has already
  // been initialized, otherwise saves the |closure| for running later, when the
  // class is initialized.
  void RunOnInitialization(base::OnceClosure closure);

  // Does the actual work of upserting an account and performing related tasks
  // like revoking old tokens and informing observers. All account updates
  // funnel through to this method. Assumes that |AccountManager| initialization
  // (|init_state_|) is complete.
  void UpsertAccountInternal(const ::account_manager::AccountKey& account_key,
                             const AccountInfo& account);

  // Posts a task on |task_runner_|, which is usually a background thread, to
  // persist the current state of |accounts_|.
  void PersistAccountsAsync();

  // Gets a serialized representation of accounts.
  std::string GetSerializedAccounts();

  // Gets the publicly viewable information stored in `accounts_`.
  std::vector<::account_manager::Account> GetAccountsView();

  // Notifies |Observer|s about a token update for |account|.
  void NotifyTokenObservers(const ::account_manager::Account& account);

  // Notifies |Observer|s about an |account| removal.
  void NotifyAccountRemovalObservers(const ::account_manager::Account& account);

  // Revokes |account_key|'s token on the relevant backend.
  // Note: Does not do anything if the |account_manager::AccountType|
  // of |account_key| does not support server token revocation.
  // Note: |account_key| may or may not be present in |accounts_|. Call this
  // method *after* modifying or deleting old tokens from |accounts_|.
  void MaybeRevokeTokenOnServer(
      const ::account_manager::AccountKey& account_key,
      const std::string& old_token);

  // Revokes |refresh_token| with GAIA. Virtual for testing.
  virtual void RevokeGaiaTokenOnServer(const std::string& refresh_token);

  // Called by |GaiaTokenRevocationRequest| to notify its request completion.
  // Deletes |request| from |pending_token_revocation_requests_|, if present.
  void DeletePendingTokenRevocationRequest(GaiaTokenRevocationRequest* request);

  // Returns |true| if |AccountManager| is operating in ephemeral / in-memory
  // mode, and not persisting anything to disk.
  bool IsEphemeralMode() const;

  // Returns the refresh token for `account_key`, if present. `account_key` must
  // be a Gaia account. Assumes that `AccountManager` initialization
  // (`init_state_`) is complete.
  std::optional<std::string> GetRefreshToken(
      const ::account_manager::AccountKey& account_key);

  // Returns `url_loader_factory_`. Assumes that `AccountManager` initialization
  // (`init_state_`) is complete.
  scoped_refptr<network::SharedURLLoaderFactory> GetUrlLoaderFactory();

  // Status of this object's initialization.
  InitializationState init_state_ = InitializationState::kNotStarted;

  // All tokens, if channel bound, are bound to |url_loader_factory_|.
  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;

  // An indirect way to access `ash::DelayNetworkCall`. We cannot use
  // `ash::DelayNetworkCall` directly here due to linking/dependency
  // issues.
  DelayNetworkCallRunner delay_network_call_runner_;

  // Non-owning pointer.
  raw_ptr<PrefService, DanglingUntriaged> pref_service_ = nullptr;

  // A task runner for disk I/O.
  // Will be |nullptr| if |AccountManager| is operating in ephemeral mode.
  scoped_refptr<base::SequencedTaskRunner> task_runner_;

  // Writes |AccountManager|'s state to disk.
  // Will be |nullptr| if |AccountManager| is operating in ephemeral mode.
  std::unique_ptr<base::ImportantFileWriter> writer_;

  // Cryptohome root.
  // Will be |base::FilePath::empty()| if |AccountManager| is operating in
  // ephemeral mode.
  base::FilePath home_dir_;

  // A map from |AccountKey|s to |AccountInfo|.
  AccountMap accounts_;

  // Callbacks waiting on class initialization (|init_state_|).
  std::vector<base::OnceClosure> initialization_callbacks_;

  // A list of |AccountManager| observers.
  // Verifies that the list is empty on destruction.
  base::ObserverList<Observer, true /* check_empty */>::Unchecked observers_;

  // A list of pending token revocation requests.
  // |AccountManager| is a long living object in general and these requests are
  // basically one shot fire-and-forget requests but for ASAN tests, we do not
  // want to have dangling pointers to pending requests.
  std::vector<std::unique_ptr<GaiaTokenRevocationRequest>>
      pending_token_revocation_requests_;

  SEQUENCE_CHECKER(sequence_checker_);

  base::WeakPtrFactory<AccountManager> weak_factory_{this};
};

}  // namespace account_manager

#endif  // COMPONENTS_ACCOUNT_MANAGER_CORE_CHROMEOS_ACCOUNT_MANAGER_H_