chromium/chrome/browser/password_manager/android/password_store_android_backend.h

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

#ifndef CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_H_
#define CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_H_

#include <memory>
#include <optional>
#include <unordered_map>

#include "base/containers/small_map.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/thread_annotations.h"
#include "base/time/time.h"
#include "base/types/pass_key.h"
#include "base/types/strong_alias.h"
#include "chrome/browser/password_manager/android/password_manager_lifecycle_helper.h"
#include "chrome/browser/password_manager/android/password_store_android_backend_api_error_codes.h"
#include "chrome/browser/password_manager/android/password_store_android_backend_bridge_helper.h"
#include "chrome/browser/password_manager/android/password_store_android_backend_dispatcher_bridge.h"
#include "components/password_manager/core/browser/password_store/password_store_backend_metrics_recorder.h"

class PrefService;

namespace password_manager {

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused. Update enums.xml whenever updating
// this enum.
enum class UnifiedPasswordManagerActiveStatus {
  // UPM is active.
  kActive = 0,
  // UPM is inactive because passwords sync is off.
  kInactiveSyncOff = 1,
  // UPM is inactive because the client has been unenrolled due to unresolvable
  // errors
  kInactiveUnenrolledDueToErrors = 2,

  kMaxValue = kInactiveUnenrolledDueToErrors
};

// This enum is used in the JobReturnHandler for tracking the store operation
// that started the job so that the correct operation can be retried when the
// job encountered an error.
enum class PasswordStoreOperation {
  // Operations that are safe to retry because they are non-modifying and not
  // visible to the user.
  kGetAllLoginsAsync = 0,
  kGetAutofillableLoginsAsync = 1,

  // Obsolete
  // kGetAllLoginsForAccountAsync = 2,

  // Operation that is non-modifying, but not safe to retry because it is
  // user-visible.
  kFillMatchingLoginsAsync = 3,

  // Operations that are not safe to retry because they are modifying.
  kAddLoginAsync = 4,
  kUpdateLoginAsync = 5,

  // Obsolete
  // kRemoveLoginForAccount = 6,

  kRemoveLoginAsync = 7,
  kRemoveLoginsByURLAndTimeAsync = 8,
  kRemoveLoginsCreatedBetweenAsync = 9,
  kDisableAutoSignInForOriginsAsync = 10,
  // Deprecated
  // kClearAllLocalPasswords = 11,

  // Operation that is non-modifying, but not safe to retry because it is
  // user-visible.
  kGetGroupedMatchingLoginsAsync = 12,
  kGetAllLoginsWithBrandingInfoAsync = 13,

  kMaxValue = kGetAllLoginsWithBrandingInfoAsync,
};

// Android-specific password store backend that delegates every request to
// Google Mobile Service.
// It uses a `PasswordStoreAndroidBackendDispatcherBridge` to send API requests
// for each method it implements from `PasswordStoreBackend`. The response will
// invoke a consumer method via `PasswordStoreAndroidBackendReceiverBridge` with
// an originally provided `JobId`. Based on that `JobId`, this class maps
// ongoing jobs to the callbacks of the methods that originally required the job
// since JNI itself can't preserve the callbacks.
class PasswordStoreAndroidBackend
    : public PasswordStoreAndroidBackendReceiverBridge::Consumer {
 protected:
  PasswordStoreAndroidBackend(
      std::unique_ptr<PasswordStoreAndroidBackendBridgeHelper> bridge_helper,
      std::unique_ptr<PasswordManagerLifecycleHelper> lifecycle_helper,
      PrefService* prefs);
  ~PasswordStoreAndroidBackend() override;

  // Internal methods corresponding to PasswordStoreBackendInterface that take
  // the account corresponding to the GMS Core storage as a param.
  void Init(
      PasswordStoreBackend::RemoteChangesReceived remote_form_changes_received);
  void Shutdown(base::OnceClosure shutdown_completed);
  void GetAllLoginsInternal(std::string account,
                            LoginsOrErrorReply callback,
                            PasswordStoreOperation operation =
                                PasswordStoreOperation::kGetAllLoginsAsync,
                            base::TimeDelta delay = base::Seconds(0));
  void GetAutofillableLoginsInternal(
      std::string account,
      LoginsOrErrorReply callback,
      PasswordStoreOperation operation =
          PasswordStoreOperation::kGetAutofillableLoginsAsync,
      base::TimeDelta delay = base::Seconds(0));
  void GetAllLoginsWithAffiliationAndBrandingInternal(
      std::string account,
      LoginsOrErrorReply callback);
  void GetLoginsInternal(std::string account,
                         const PasswordFormDigest& form,
                         bool include_psl,
                         LoginsOrErrorReply callback);
  void AddLoginInternal(std::string account,
                        const PasswordForm& form,
                        PasswordChangesOrErrorReply callback);
  void UpdateLoginInternal(std::string account,
                           const PasswordForm& form,
                           PasswordChangesOrErrorReply callback);
  void RemoveLoginInternal(std::string account,
                           const PasswordForm& form,
                           PasswordChangesOrErrorReply callback);
  void FillMatchingLoginsInternal(std::string account,
                                  LoginsOrErrorReply callback,
                                  bool include_psl,
                                  const std::vector<PasswordFormDigest>& forms);
  void GetGroupedMatchingLoginsInternal(std::string account,
                                        const PasswordFormDigest& form_digest,
                                        LoginsOrErrorReply callback);
  void RemoveLoginsByURLAndTimeInternal(
      std::string account,
      const base::RepeatingCallback<bool(const GURL&)>& url_filter,
      base::Time delete_begin,
      base::Time delete_end,
      PasswordChangesOrErrorReply callback);
  void RemoveLoginsCreatedBetweenInternal(std::string account,
                                          base::Time delete_begin,
                                          base::Time delete_end,
                                          PasswordChangesOrErrorReply callback);
  void DisableAutoSignInForOriginsInternal(
      std::string account,
      const base::RepeatingCallback<bool(const GURL&)>& origin_filter,
      base::OnceClosure completion);

  // Cancels all the queued jobs because of `reason` and replies to them with
  // `reply_error`.
  void ClearAllTasksAndReplyWithReason(
      const AndroidBackendError& reason,
      const PasswordStoreBackendError& reply_error);

  PasswordStoreAndroidBackendBridgeHelper* bridge_helper() {
    return bridge_helper_.get();
  }

  PrefService* prefs() { return prefs_; }

  // Subclasses can override this method
  // to have a special handling for different errors.
  virtual void RecoverOnError(AndroidBackendAPIErrorCode error) = 0;
  // Subclasses can override this method to react when GMSCore responds
  // successfully.
  virtual void OnCallToGMSCoreSucceeded() = 0;
  // Subclasses have to provide an account which will be used for retries.
  virtual std::string GetAccountToRetryOperation() = 0;
  // Subclasses have to provide a store backend type that is used for tracking
  // metrics that are split for local and account.
  virtual PasswordStoreBackendMetricsRecorder::PasswordStoreAndroidBackendType
  GetStorageType() = 0;

 private:
  SEQUENCE_CHECKER(main_sequence_checker_);

  // Wraps the handler for an asynchronous job (if successful or scheduled to be
  // retried) and invokes the supplied metrics recorded upon completion. An
  // object of this type shall be created and stored in |request_for_job_| once
  // an asynchronous task begins, and destroyed once the job is finished.
  // The handler stores an |operation| which determines whether the method
  // matching the |operation| can be retried due to an error. The operations are
  // retried with exponential backoff. The |delay| with which the job was
  // started is stored in the handler so that the next retry of the job can
  // increase the |delay|. NOTE: Currently only retries for operations which
  // match 1-to-1 to methods are supported.
  class JobReturnHandler {
   public:
    using ErrorReply = base::OnceClosure;

    JobReturnHandler(LoginsOrErrorReply callback,
                     PasswordStoreBackendMetricsRecorder metrics_recorder,
                     base::TimeDelta delay,
                     PasswordStoreOperation operation);
    JobReturnHandler(PasswordChangesOrErrorReply callback,
                     PasswordStoreBackendMetricsRecorder metrics_recorder,
                     base::TimeDelta delay,
                     PasswordStoreOperation operation);
    JobReturnHandler(JobReturnHandler&&);
    JobReturnHandler& operator=(JobReturnHandler&&) = delete;
    ~JobReturnHandler();

    template <typename T>
    bool Holds() const {
      return absl::holds_alternative<T>(success_callback_);
    }

    template <typename T>
    T&& Get() && {
      return std::move(absl::get<T>(success_callback_));
    }

    void RecordMetrics(std::optional<AndroidBackendError> error) const;
    base::TimeDelta GetElapsedTimeSinceStart() const;

    base::TimeDelta GetDelay();
    PasswordStoreOperation GetOperation();

   private:
    absl::variant<LoginsOrErrorReply, PasswordChangesOrErrorReply>
        success_callback_;
    PasswordStoreBackendMetricsRecorder metrics_recorder_;
    base::TimeDelta delay_;
    PasswordStoreOperation operation_;
  };

  // Wraps the `callback` to retry after the previous attempt to call
  // the backend failed. Used to be able to cancel a posted delayed
  // operation while also replying to its `reply_callback` that the
  // delayed retry was cancelled. In order to cancel the retry, it's
  // enough to destroy the wrapper object, which will invalidate the weak
  // pointer bound to the delayed task.
  class CancellableRetryCallback {
   public:
    CancellableRetryCallback(base::OnceCallback<void(LoginsOrErrorReply,
                                                     PasswordStoreOperation,
                                                     base::TimeDelta)> callback,
                             PasswordStoreOperation operation,
                             LoginsOrErrorReply reply_callback,
                             base::TimeDelta current_delay);
    CancellableRetryCallback(CancellableRetryCallback&&) = delete;
    CancellableRetryCallback& operator=(CancellableRetryCallback&&) = delete;
    ~CancellableRetryCallback();

    base::WeakPtr<CancellableRetryCallback> AsWeakPtr();

    void Run();
    LoginsOrErrorReply GetReplyCallbackAndCancel();

   private:
    base::OnceCallback<
        void(LoginsOrErrorReply, PasswordStoreOperation, base::TimeDelta)>
        callback_;
    PasswordStoreOperation operation_;
    LoginsOrErrorReply reply_callback_;
    base::TimeDelta current_delay_;
    base::WeakPtrFactory<CancellableRetryCallback> weak_ptr_factory_{this};
  };

  using JobId = PasswordStoreAndroidBackendDispatcherBridge::JobId;
  // Using a small_map should ensure that we handle rare cases with many jobs
  // like a bulk deletion just as well as the normal, rather small job load.
  using JobMap = base::small_map<
      std::unordered_map<JobId, JobReturnHandler, JobId::Hasher>>;

  using DelayedRetryId = base::IdType32<CancellableRetryCallback>;

  base::OnceCallback<
      void(LoginsOrErrorReply, PasswordStoreOperation, base::TimeDelta)>
  GetRetryCallbackForOperation(PasswordStoreOperation operation);
  // Implements the retry mechanism for the operations that are safe to retry.
  // It attempts to retry the |operation|. The given |delay| comes from the
  // previous attempt to run the operation. The delay before the next retry will
  // be double the value of |delay|, except for the first retry that has a delay
  // of 1 second.
  void RetryOperation(PasswordStoreOperation operation,
                      AndroidBackendAPIErrorCode api_error_code,
                      base::TimeDelta delay,
                      LoginsOrErrorReply reply);
  void CleanupRetryAfterRun(DelayedRetryId retry_id);

  // Implements PasswordStoreAndroidBackendDispatcherBridge::Consumer interface.
  void OnCompleteWithLogins(
      PasswordStoreAndroidBackendDispatcherBridge::JobId job_id,
      std::vector<PasswordForm> passwords) override;
  void OnLoginsChanged(
      PasswordStoreAndroidBackendDispatcherBridge::JobId task_id,
      PasswordChanges changes) override;
  void OnError(PasswordStoreAndroidBackendDispatcherBridge::JobId job_id,
               AndroidBackendError error) override;

  template <typename Callback>
  // Calling this method can be delayed in the case when a retry is scheduled.
  // Since the retry logic implements exponential backoff the duration of the
  // |delay| is passed to QueueNewJob from its caller such that the |delay| can
  // be stored in the JobReturnHandler and retrievable from it. QueueNewJob
  // doesn't introduce any delay.
  void QueueNewJob(JobId job_id,
                   Callback callback,
                   MethodName method_name,
                   PasswordStoreOperation operation,
                   base::TimeDelta delay);
  std::optional<JobReturnHandler> GetAndEraseJob(JobId job_id);

  // Filters |logins| created between |delete_begin| and |delete_end| time
  // that match |url_filer| and asynchronously removes them.
  // |operation| is the PasswordStoreOperation  that invoked this method and
  // |delay| is the amount of time by which the call to this method was delayed.
  void FilterAndRemoveLogins(
      std::string account,
      const base::RepeatingCallback<bool(const GURL&)>& url_filter,
      base::Time delete_begin,
      base::Time delete_end,
      PasswordChangesOrErrorReply reply,
      LoginsResultOrError result);

  // Filters logins that match |origin_filer| and asynchronously disables
  // autosignin by updating stored logins.
  void FilterAndDisableAutoSignIn(
      std::string account,
      const base::RepeatingCallback<bool(const GURL&)>& origin_filter,
      PasswordChangesOrErrorReply completion,
      LoginsResultOrError result);

  // Creates a metrics recorder that records latency and success metrics for
  // logins retrieval operation with |method_name| name prior to calling
  // |callback|.
  static LoginsOrErrorReply ReportMetricsAndInvokeCallbackForLoginsRetrieval(
      const MethodName& method_name,
      LoginsOrErrorReply callback,
      PasswordStoreBackendMetricsRecorder::PasswordStoreAndroidBackendType
          store_type);

  // Creates a metrics recorder that records latency and success metrics for
  // store modification operation with |method_name| name prior to
  // calling |callback|.
  static PasswordChangesOrErrorReply
  ReportMetricsAndInvokeCallbackForStoreModifications(
      const MethodName& method_name,
      PasswordChangesOrErrorReply callback,
      PasswordStoreBackendMetricsRecorder::PasswordStoreAndroidBackendType
          store_type);

  // Invoked synchronously by `lifecycle_helper_` when Chrome is foregrounded.
  // This should not cover the initial startup since the registration for the
  // event happens afterwads and is not repeated. A "foreground session" starts
  // when a Chrome activity resumes for the first time.
  void OnForegroundSessionStart();

  // Clears all `request_for_job_`s that haven't been completed yet at a moment
  // when it's unlikely that they will still finish. It records an error for
  // each task cleared this way that it could have failed.
  void ClearZombieTasks();

  // Observer to propagate potential password changes to.
  PasswordStoreBackend::RemoteChangesReceived stored_passwords_changed_;

  // Helper that receives lifecycle events via JNI and synchronously invokes a
  // passed callback, e.g. `OnForegroundSessionStart`.
  std::unique_ptr<PasswordManagerLifecycleHelper> lifecycle_helper_;

  // TaskRunner to run responses on the correct thread.
  scoped_refptr<base::SequencedTaskRunner> main_task_runner_;

  // Used to store callbacks for each invoked jobs since callbacks can't be
  // called via JNI directly.
  JobMap request_for_job_ GUARDED_BY_CONTEXT(main_sequence_checker_);

  // This object is the proxy to the dispatcher JNI bridge that performs the API
  // requests.
  std::unique_ptr<PasswordStoreAndroidBackendBridgeHelper> bridge_helper_;

  // Used to store retry callbacks that will be executed as part of a
  // posted delayed task.
  std::map<DelayedRetryId, std::unique_ptr<CancellableRetryCallback>>
      scheduled_retries_ GUARDED_BY_CONTEXT(main_sequence_checker_);

  // The id of the latest scheduled retry. Incremented when a new retry is
  // scheduled.
  DelayedRetryId::Generator delayed_retry_id_generator_;

  raw_ptr<PrefService> prefs_ = nullptr;

  base::Time initialized_at_ = base::Time::Now();

  // This will be set to false once the first foregrounding has been handled.
  bool should_delay_refresh_on_foregrounding_ = true;

  base::WeakPtrFactory<PasswordStoreAndroidBackend> weak_ptr_factory_{this};
};

}  // namespace password_manager

#endif  // CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_H_