chromium/ios/chrome/browser/safety_check/model/ios_chrome_safety_check_manager.h

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

#ifndef IOS_CHROME_BROWSER_SAFETY_CHECK_MODEL_IOS_CHROME_SAFETY_CHECK_MANAGER_H_
#define IOS_CHROME_BROWSER_SAFETY_CHECK_MODEL_IOS_CHROME_SAFETY_CHECK_MANAGER_H_

#import "base/memory/scoped_refptr.h"
#import "base/memory/weak_ptr.h"
#import "base/observer_list.h"
#import "base/observer_list_types.h"
#import "base/sequence_checker.h"
#import "base/task/sequenced_task_runner.h"
#import "components/keyed_service/core/keyed_service.h"
#import "components/prefs/pref_change_registrar.h"
#import "ios/chrome/browser/omaha/model/omaha_service.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_password_check_manager.h"
#import "ios/chrome/browser/passwords/model/password_checkup_utils.h"
#import "ios/chrome/browser/safety_check/model/ios_chrome_safety_check_manager_constants.h"

class IOSChromeSafetyCheckManager;
class PrefService;
struct UpgradeRecommendedDetails;

// All IOSChromeSafetyCheckManagerObserver events will be evaluated on the
// same sequence the IOSChromeSafetyCheckManager is created on.
class IOSChromeSafetyCheckManagerObserver : public base::CheckedObserver {
 public:
  // Called whenever the Safety Check determines a change in the Password check
  // state (i.e. when the user has reused passwords, weak passwords, no
  // compromised password, etc.). Also provides the latest count of insecure
  // credentials.
  virtual void PasswordCheckStateChanged(
      PasswordSafetyCheckState state,
      password_manager::InsecurePasswordCounts insecure_password_counts) {}
  // Called whenever the Safety Check determines a change in the Safe Browsing
  // check state (i.e. when Safe Browsing is enabled, disabled, the check
  // is currently running, etc.)
  virtual void SafeBrowsingCheckStateChanged(
      SafeBrowsingSafetyCheckState state) {}
  // Called whenever the Safety Check determines a change in the Update Chrome
  // check state (i.e. when Chrome is up to date, Chrome is out of date, the
  // check is currently running, etc.)
  virtual void UpdateChromeCheckStateChanged(
      UpdateChromeSafetyCheckState state) {}
  // Called whenever the Safety Check begins the async process of evaluating the
  // Password check, Safe Browsing check, and/or Update check.
  virtual void RunningStateChanged(RunningSafetyCheckState state) {}
  // Notifies the observer that `safety_check_manager` has begun shutting down.
  // Observers should remove themselves from the manager via
  // `safety_check_manager->RemoveObserver(...)` when this happens.
  virtual void ManagerWillShutdown(
      IOSChromeSafetyCheckManager* safety_check_manager) {}
};

// This class handles the bulk of the safety check feature, namely:
//
// 1. Monitors:
//    - Password check status and compromised credentials list
//    - Enhanced Safe Browsing enablement
//    - App update status
//    - Safety Check execution status (running/complete)
//    - Results of previous Safety Check runs
//
// 2. Automatic Safety Check Runs:
//    - Triggers Safety Check automatically if the last run is older than
//      'kSafetyCheckAutorunDelay' (e.g., 30 days), regardless of whether the
//      previous run was manual or automatic.
//
// 3. Observer Notifications:
//    - Notifies `IOSChromeSafetyCheckManagerObserver` of any state changes
//      (e.g., compromised password detected, Safety Check completed).
class IOSChromeSafetyCheckManager
    : public KeyedService,
      public IOSChromePasswordCheckManager::Observer,
      public OmahaServiceObserver {
 public:
  explicit IOSChromeSafetyCheckManager(
      PrefService* pref_service,
      PrefService* local_pref_service,
      scoped_refptr<IOSChromePasswordCheckManager> password_check_manager,
      scoped_refptr<base::SequencedTaskRunner> task_runner);

  IOSChromeSafetyCheckManager(const IOSChromeSafetyCheckManager&) = delete;
  IOSChromeSafetyCheckManager& operator=(const IOSChromeSafetyCheckManager&) =
      delete;

  ~IOSChromeSafetyCheckManager() override;

  // KeyedService implementation.
  void Shutdown() override;

  // Starts the Safety Check, which comprises starting
  // the: [1] Safe Browsing check, [2] Update Chrome check, and [3] Passwords
  // Check, and notifies any observers of the change.
  //
  // NOTE: If the Safety Check is already running, does nothing.
  void StartSafetyCheck();

  // Stops the currently running Safety Check, if any, which comprises stopping
  // the: [1] Safe Browsing check, [2] Update Chrome check, and [3] Passwords
  // Check, and notifies any observers of the change.
  //
  // NOTE: If the Safety Check is not currently running, does nothing.
  void StopSafetyCheck();

  // `IOSChromePasswordCheckManager::Observer` implementation.
  void PasswordCheckStatusChanged(PasswordCheckState state) override;
  void InsecureCredentialsChanged() override;
  void ManagerWillShutdown(
      IOSChromePasswordCheckManager* password_check_manager) override;

  // `OmahaServiceObserver` implementation.
  void UpgradeRecommendedDetailsChanged(
      UpgradeRecommendedDetails details) override;
  void ServiceWillShutdown(OmahaService* omaha_service) override;

  // Adds/removes an observer to be notified of PasswordSafetyCheckState,
  // SafeBrowsingSafetyCheckState, UpdateChromeSafetyCheckState, and
  // RunningSafetyCheckState events.
  void AddObserver(IOSChromeSafetyCheckManagerObserver* observer);
  void RemoveObserver(IOSChromeSafetyCheckManagerObserver* observer);

  // Returns the current state of the Safe Browsing check.
  SafeBrowsingSafetyCheckState GetSafeBrowsingCheckState() const;

  // Returns the current state of the Password check.
  PasswordSafetyCheckState GetPasswordCheckState() const;

  // Returns the current state of the Update Chrome check.
  UpdateChromeSafetyCheckState GetUpdateChromeCheckState() const;

  // Returns the current insecure password counts.
  password_manager::InsecurePasswordCounts GetInsecurePasswordCounts() const;

  // Returns the App Store Chrome upgrade URL.
  const GURL& GetChromeAppUpgradeUrl() const;

  // Returns the next Chrome app version.
  std::string GetChromeAppNextVersion() const;

  // Returns all insecure credentials that are present, provided by the Password
  // Check Manager.
  std::vector<password_manager::CredentialUIEntry> GetInsecureCredentials()
      const;

  // Returns the time of the last Safety Check run, if ever.
  base::Time GetLastSafetyCheckRunTime() const;

  // Ingests the Omaha response, `details`, to determine if the app is up to
  // date.
  //
  // If the app is up-to-date, calls `SetUpdateChromeCheckState()` to reflect
  // the new, updated state.
  //
  // If the app is outdated, sets `upgrade_url_` and `next_version_` to maintain
  // the upgrade details.
  void HandleOmahaResponse(UpgradeRecommendedDetails details);

  // For unit-testing only.
  void StartOmahaCheckForTesting();
  RunningSafetyCheckState GetRunningCheckStateForTesting() const;
  void SetPasswordCheckStateForTesting(PasswordSafetyCheckState state);
  void SetInsecurePasswordCountsForTesting(
      password_manager::InsecurePasswordCounts counts);
  void PasswordCheckStatusChangedForTesting(PasswordCheckState state);
  void InsecureCredentialsChangedForTesting();
  void RestorePreviousSafetyCheckStateForTesting();

 private:
  // Restores the Safety Check Manager with the previous check states, if any,
  // from Prefs.
  void RestorePreviousSafetyCheckState();

  // Starts the asynchronous Password check, and notifies any observers of the
  // change.
  void StartPasswordCheck();

  // Stops the currently running Password check, if any, and notifies any
  // observers of the change.
  void StopPasswordCheck();

  // Starts the asynchronous Update Chrome check, and notifies any observers of
  // the change.
  void StartUpdateChromeCheck();

  // Stops the currently running Update Chrome check, if any, and notifies any
  // observers of the change.
  void StopUpdateChromeCheck();

  // Sets `safe_browsing_check_state_` to `state` and notifies any observers
  // of the change.
  void SetSafeBrowsingCheckState(SafeBrowsingSafetyCheckState state);

  // Updates `password_check_state_` to `state` and notifies any observers
  // of the change.
  void SetPasswordCheckState(PasswordSafetyCheckState state);

  // Updates `insecure_password_counts_` to `counts`. Does not notify any
  // observers of the change.
  void SetInsecurePasswordCounts(
      password_manager::InsecurePasswordCounts counts);

  // Updates `update_chrome_check_state_` to `state` and notifies any observers
  // of the change.
  void SetUpdateChromeCheckState(UpdateChromeSafetyCheckState state);

  // Converts `state` (`PasswordCheckState`) to type
  // `PasswordSafetyCheckState`, then calls
  // `SetPasswordCheckState(PasswordSafetyCheckState state)`.
  void ConvertAndSetPasswordCheckState(PasswordCheckState state);

  // Updates `password_check_state_` to the latest, correct value based on
  // changes in the insecure credentials list. Then calls
  // `SetPasswordCheckState(PasswordSafetyCheckState state)`.
  //
  // NOTE: This method exists to cover an edge case where the insecure
  // credentials list may change while the Password check is currently running.
  void RefreshOutdatedPasswordCheckState();

  // Reads the latest Safe Browsing values from Prefs, and updates the internal
  // Safe Browsing check state.
  void UpdateSafeBrowsingCheckState();

  // Sets the app's upgrade details provided by the Omaha service.
  void SetUpdateChromeDetails(GURL upgrade_url, std::string next_version);

  // Starts an async request to the Omaha service for whether the current device
  // is up to date.
  //
  // NOTE: The response from the Omaha service is handled by
  // `HandleOmahaResponse()`.
  void StartOmahaCheck();

  // Checks if the Update Chrome check is still running after
  // `kOmahaNetworkWaitTime` has elapsed. If so, considers this an Omaha
  // error, and updates all relevant state to reflect the error. (If not, does
  // nothing.)
  //
  // NOTE: This method is called `kOmahaNetworkWaitTime` after
  // `StartOmahaCheck()`.
  void HandleOmahaError();

  // Checks if any checks are currently running. If so, sets
  // `running_safety_check_state_` to the running state, and notifies any
  // observers of the change.
  void RefreshSafetyCheckRunningState();

  // Logs the time of the current Safety Check run to Prefs.
  void LogCurrentSafetyCheckRunTime();

  // Current running state of the Safety Check. If any checks are currently
  // running (e.g. Safe Browsing check, Update Chrome check, Passwords Check),
  // then the Safety Check is considered to be running, too. For example:
  //
  //               (✓ = running, x = not running)
  //
  // +--------+----------+---------------+-------+--------------+
  // | Update | Password | Safe Browsing |       | Safety Check |
  // +--------+----------+---------------+-------+--------------+
  // |    ✓   |     ✓    |       ✓       |   =   |       ✓      |
  // +--------+----------+---------------+-------+--------------+
  // |    x   |     ✓    |       ✓       |   =   |       ✓      |
  // +--------+----------+---------------+-------+--------------+
  // |    ✓   |     x    |       ✓       |   =   |       ✓      |
  // +--------+----------+---------------+-------+--------------+
  // |    ✓   |     ✓    |       x       |   =   |       ✓      |
  // +--------+----------+---------------+-------+--------------+
  // |    x   |     x    |       ✓       |   =   |       ✓      |
  // +--------+----------+---------------+-------+--------------+
  // |    ✓   |     x    |       x       |   =   |       ✓      |
  // +--------+----------+---------------+-------+--------------+
  // |    x   |     ✓    |       x       |   =   |       ✓      |
  // +--------+----------+---------------+-------+--------------+
  // |    x   |     x    |       x       |   =   |       x      |
  // +--------+----------+---------------+-------+--------------+
  RunningSafetyCheckState running_safety_check_state_ =
      RunningSafetyCheckState::kDefault;

  // Current state of the Safe Browsing check.
  SafeBrowsingSafetyCheckState safe_browsing_check_state_ =
      SafeBrowsingSafetyCheckState::kDefault;

  // Current state of the Password check.
  PasswordSafetyCheckState password_check_state_ =
      PasswordSafetyCheckState::kDefault;

  // Previous state of the Password check. (Used as a fallback state, which
  // enables users to cancel a currently running Password check.)
  PasswordSafetyCheckState previous_password_check_state_ =
      PasswordSafetyCheckState::kDefault;

  // Current state of the Update Chrome check.
  UpdateChromeSafetyCheckState update_chrome_check_state_ =
      UpdateChromeSafetyCheckState::kDefault;

  // Previous state of the Update Chrome check. (Used as a fallback state, which
  // enables users to cancel a currently running Update Chrome check.)
  UpdateChromeSafetyCheckState previous_update_chrome_check_state_ =
      UpdateChromeSafetyCheckState::kDefault;

  // The count of passwords flagged as compromised, dismissed, reused, and weak
  // by the most recent Safety Check run.
  password_manager::InsecurePasswordCounts insecure_password_counts_ = {
      /* compromised */ 0, /* dismissed */ 0, /* reused */ 0,
      /* weak */ 0};

  // The count of passwords flagged as compromised, dismissed, reused, and weak
  // by the previous Safety Check run.
  password_manager::InsecurePasswordCounts previous_insecure_password_counts_ =
      {/* compromised */ 0, /* dismissed */ 0, /* reused */ 0,
       /* weak */ 0};

  // The last time the Safety Check was run, if ever.
  base::Time last_safety_check_run_time_;

  // If `ignore_omaha_changes_` is true when either
  // `HandleOmahaResponse()` or `HandleOmahaError()` are called, nothing
  // happens. Effectively, this enables users to cancel a currently running
  // Update Chrome check.
  //
  // NOTE: `ignore_omaha_changes_` is reset to false when the Safety Check is
  // run again.
  bool ignore_omaha_changes_ = false;

  // If `ignore_password_check_changes_` is true when Password Check Manager
  // observer methods are called, nothing happens. Effectively, this enables
  // users to cancel a currently running Password check.
  //
  // NOTE: `ignore_password_check_changes_` is reset to false when the Safety
  // Check is run again.
  bool ignore_password_check_changes_ = false;

  // The app upgrade URL generated by the Omaha service.
  //
  // NOTE: This may be an empty, invalid URL, which doesn't necessarily indicate
  // an issue. Rather, an empty, invalid URL likely means the URL was never set
  // because the app is already up to date.
  GURL upgrade_url_;

  // The app's next version generated by the Omaha service.
  //
  // NOTE: This may be empty, which doesn't necessarily indicate
  // an issue. Rather, it likely means the next version was never set
  // because the app is already up to date.
  std::string next_version_;

  // Observers to listen to Safety Check changes.
  base::ObserverList<IOSChromeSafetyCheckManagerObserver, true> observers_;

  // Weak pointer to the pref service, which checks the user's Enhanced Safe
  // Browsing state.
  raw_ptr<PrefService> pref_service_;

  // Weak pointer to the local-state pref service, which stores information
  // about the latest Safety Check run (e.g. the results of each check, the
  // timestamp of the run, etc.)
  raw_ptr<PrefService> local_pref_service_;

  // Refcounted pointer to the IOSChromeSafetyCheckManager to use.
  scoped_refptr<IOSChromePasswordCheckManager> password_check_manager_;

  // Registrar for pref changes notifications.
  PrefChangeRegistrar pref_change_registrar_;

  // Validates IOSChromeSafetyCheckManager::Observer events are evaluated on the
  // same sequence that IOSChromeSafetyCheckManager was created on.
  SEQUENCE_CHECKER(sequence_checker_);

  // Ensures IOSChromePasswordCheckManager::Observer events are posted on the
  // same sequence that IOSChromeSafetyCheckManager was created on.
  const scoped_refptr<base::SequencedTaskRunner> task_runner_;

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

#endif  // IOS_CHROME_BROWSER_SAFETY_CHECK_MODEL_IOS_CHROME_SAFETY_CHECK_MANAGER_H_