// 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_SAFE_BROWSING_EXTENSION_TELEMETRY_EXTENSION_TELEMETRY_SERVICE_H_
#define CHROME_BROWSER_SAFE_BROWSING_EXTENSION_TELEMETRY_EXTENSION_TELEMETRY_SERVICE_H_

#include <memory>
#include <string>

#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/files/file_path.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/sequence_bound.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/types/optional_ref.h"
#include "base/values.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/prefs/pref_change_registrar.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/extension_set.h"

struct AutocompleteMatch;

class Profile;
class PrefService;

namespace extensions {
class Extension;
class ExtensionPrefs;
class ExtensionRegistry;
}  // namespace extensions

namespace network {
class SharedURLLoaderFactory;
}  // namespace network

namespace safe_browsing {

enum class ExtensionSignalType;
class ExtensionSignal;
class ExtensionSignalProcessor;
class ExtensionTelemetryConfigManager;
class ExtensionTelemetryFileProcessor;
class ExtensionTelemetryPersister;
class ExtensionTelemetryReportRequest;
class ExtensionTelemetryReportRequest_ExtensionInfo;
class ExtensionTelemetryReportRequest_ExtensionInfo_FileInfo;
class ExtensionTelemetryReportResponse;
class ExtensionTelemetryUploader;
class SafeBrowsingTokenFetcher;
class SearchHijackingDetector;
class ActivityLogIngester;

// This class processes extension signals and reports telemetry for a given
// profile (regular profile only). It is used exclusively on the UI thread.
// Lifetime:
// The service is instantiated when the associated profile is instantiated. It
// is destructed when the corresponding profile is destructed.
// Enable/Disable states:
// The service is enabled if:
//  - the user is opted into Enhanced Safe Browsing (ESB) OR
//  - enterprise telemetry is enabled by a policy
//
// |esb_enabled_| - Generates telemetry reports for Enhanced Safe Browsing
// (ESB) users. When enabled, the service receives/stores signal
// information, and collects file data for off-store extensions.
// Periodically, the telemetry reports are uploaded to the SB servers. In
// the upload response, the CRX telemetry server includes unsafe off-store
// extension verdicts that the service can take action on.
// |enterprise_enabled_| - Generates enterprise telemetry reports for
// managed profiles. When enabled, the service also collects signal
// information and file data for off-store extensions. Periodically, the
// telemetry reports are sent to the Chrome Enterprise Reporting servers
// instead. Unlike the ESB flow, there is no response received when an
// enterprise report is sent.
//
// For both ESB and enterprise: when disabled, any previously stored signal
// information is cleared, incoming signals are ignored and no reports are
// sent.
class ExtensionTelemetryService : public KeyedService {
 public:
  // Convenience method to get the service for a profile.
  static ExtensionTelemetryService* Get(Profile* profile);

  ExtensionTelemetryService(ExtensionTelemetryService&&) = delete;
  ExtensionTelemetryService& operator=(ExtensionTelemetryService&&) = delete;
  ExtensionTelemetryService(
      Profile* profile,
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);

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

  ~ExtensionTelemetryService() override;

  base::TimeDelta GetEnterpriseTimerDelayForTesting() const {
    return enterprise_timer_.GetCurrentDelay();
  }

  // Records the signal type when a signal is:
  // - created externally and passed to extension service using AddSignal OR
  // - created internally by a signal processor from other signals received.
  static void RecordSignalType(ExtensionSignalType signal_type);

  // Recorded when a signal is discarded because it contains invalid data (e.g.,
  // invalid extension id).
  static void RecordSignalDiscarded(ExtensionSignalType signal_type);

  // Enables/disables the service for ESB reports.
  void SetEnabledForESB(bool enable);
  // Enables/disables the service for Enterprise reports.
  void SetEnabledForEnterprise(bool enable);
  // Returns true if the telemetry service is enabled for either ESB,
  // enterprise, or both.
  bool enabled() const;

  // Accepts extension telemetry signals for processing.
  virtual void AddSignal(std::unique_ptr<ExtensionSignal> signal);

  // Intercepts omnibox search events for processing.
  void OnOmniboxSearch(const AutocompleteMatch& match);

  // Called when a Search Engine Results Page (SERP) corresponding to a
  // Default Search Engine (DSE) has been loaded. Declared virtual for testing.
  virtual void OnDseSerpLoaded();

  // Checks the `extension_id` and `signal_type` against the
  // configuration and reports true if the signal should be created.
  bool IsSignalEnabled(const extensions::ExtensionId& extension_id,
                       ExtensionSignalType signal_type) const;

  base::TimeDelta current_reporting_interval() {
    return current_reporting_interval_;
  }

  // KeyedService:
  void Shutdown() override;

  base::TimeDelta GetOffstoreFileDataCollectionStartupDelaySeconds();
  base::TimeDelta GetOffstoreFileDataCollectionIntervalSeconds();

  SearchHijackingDetector* search_hijacking_detector_for_testing() {
    return search_hijacking_detector_.get();
  }

 private:
  using SignalProcessors =
      base::flat_map<ExtensionSignalType,
                     std::unique_ptr<ExtensionSignalProcessor>>;
  using SignalSubscribers = base::flat_map<
      ExtensionSignalType,
      std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>>;
  // Maps extension id to extension data.
  using ExtensionStore = base::flat_map<
      extensions::ExtensionId,
      std::unique_ptr<ExtensionTelemetryReportRequest_ExtensionInfo>>;

  // Called when the pref that affects ESB telemetry reporting is changed.
  void OnESBPrefChanged();

  // Called when the policy that affects enterprise telemetry reporting is
  // changed.
  void OnEnterprisePolicyChanged();

  // Helper method to add and process an extension signal. Shared by both ESB
  // and enterprise reporting.
  void AddSignalHelper(const ExtensionSignal& signal,
                       ExtensionStore& store,
                       SignalSubscribers& subscribers);

  // Creates a telemetry report with common fields shared by both ESB and
  // enterprise reporting.
  std::unique_ptr<ExtensionTelemetryReportRequest>
  CreateReportWithCommonFieldsPopulated();

  // Creates and uploads telemetry reports.
  void CreateAndUploadReport();

  // Creates and sends telemetry report to enterprise.
  void CreateAndSendEnterpriseReport();

  void OnUploadComplete(bool success,
                        base::optional_ref<std::string> response_data);

  // Returns a bool that represents if there is any signal processor
  // information to report.
  bool SignalDataPresent() const;

  // Creates telemetry report protobuf for all extension store extensions
  // and currently installed extensions along with signal data retrieved from
  // signal processors.
  std::unique_ptr<ExtensionTelemetryReportRequest> CreateReport();
  // For enterprise, a report is only created for extensions with signals data.
  std::unique_ptr<ExtensionTelemetryReportRequest> CreateReportForEnterprise();

  // Dumps a telemetry report in logs for testing.
  void DumpReportForTesting(const ExtensionTelemetryReportRequest& report);

  // Collects extension information for reporting.
  std::unique_ptr<ExtensionTelemetryReportRequest_ExtensionInfo>
  GetExtensionInfoForReport(const extensions::Extension& extension);

  void UploadPersistedFile(std::string report);

  // Creates access token fetcher based on profile log-in status.
  // Returns nullptr when the user is not signed in.
  std::unique_ptr<SafeBrowsingTokenFetcher> GetTokenFetcher();

  // Called periodically based on the Telemetry Service timer. If time
  // elapsed since last upload is less than the reporting interval, persists
  // a new report, else uploads current report and any persisted reports.
  void PersistOrUploadData();

  // Uploads an extension telemetry report.
  void UploadReport(std::unique_ptr<std::string> report);

  // Checks the time of the last upload and if enough time has passed,
  // uploads telemetry data. Runs on a delayed post task on startup.
  void StartUploadCheck();

  // Callback used to receive information about any extensions present in
  // the Chrome command line switch, --load-extension. The information is
  // collected off the UI thread since it involves reading the manifest file of
  // the extension. The callback stores the received information, a set of
  // extension objects, in `commandline_extensions_`.
  // NOTE: The extension objects are created without actually installing the
  // extensions.
  void OnCommandLineExtensionsInfoCollected(
      extensions::ExtensionSet commandline_extensions);

  // Sets up signal processors and subscribers for ESB telemetry.
  void SetUpSignalProcessorsAndSubscribersForESB();

  // Sets up signal processors and subscribers for enterprise telemetry.
  void SetUpSignalProcessorsAndSubscribersForEnterprise();

  // Sets up the off-store file data collection: file processor, timer, and
  // command line extensions. If it is already set up, this is a no-op.
  void SetUpOffstoreFileDataCollection();

  // Searches for offstore extensions, collects file data such as
  // hashes/manifest content, and saves the data to PrefService. Repeats every 2
  // hours. This method is repeated periodically (default 2 hours) to check if
  // the file data needs to be updated. File data is updated once every 24 hours
  // by default.
  void StartOffstoreFileDataCollection();

  // Searches through the extension registry for offstore extensions and
  // populates |offstore_extension_dirs_| with extension id and root directory.
  // An off-store extension is defined as not from the webstore and not
  // installed from components.
  void GetOffstoreExtensionDirs();

  // Remove any stale off-store file data stored in prefs. The data is
  // considered stale if the associated off-store extension is no longer
  // installed or no longer part of the --load-extension command line switch.
  void RemoveStaleExtensionsFileDataFromPref();

  // Collect file data from an offstore extension by making a call to the
  // FileProcessor.
  void CollectOffstoreFileData();

  // Stores information to identify file data collected for each offstore
  // extension.
  struct OffstoreExtensionFileDataContext {
    OffstoreExtensionFileDataContext(
        const extensions::ExtensionId& extension_id,
        const base::FilePath& root_dir);
    OffstoreExtensionFileDataContext(
        const extensions::ExtensionId& extension_id,
        const base::FilePath& root_dir,
        const base::Time& last_processed_time);

    extensions::ExtensionId extension_id;
    base::FilePath root_dir;
    base::Time last_processed_time;

    bool operator<(const OffstoreExtensionFileDataContext& other) const;
  };

  // Callback invoked when file data for an extension is completed. The data is
  // saved to Prefs and the next extension file data collection is initiated.
  void OnOffstoreFileDataCollected(
      base::flat_set<OffstoreExtensionFileDataContext>::iterator context,
      base::DictValue file_data);

  // Stops and clears any offstore file data collection objects/contexts.
  void StopOffstoreFileDataCollection();

  // Updates the state of the extension DOM activity logging based on the
  // enterprise policy and feature flag.
  void UpdateDOMActivityLoggingState();

  // Stores offstore extension file data retrieved from PrefService.
  struct OffstoreExtensionFileData {
    OffstoreExtensionFileData();
    ~OffstoreExtensionFileData();
    OffstoreExtensionFileData(const OffstoreExtensionFileData&);

    std::string manifest;
    std::vector<ExtensionTelemetryReportRequest_ExtensionInfo_FileInfo>
        file_infos;
  };

  // Given an |extension_id|, retrieves the collected file data from PrefService
  // if available.
  std::optional<OffstoreExtensionFileData> RetrieveOffstoreFileDataForReport(
      const extensions::ExtensionId& extension_id);

  // Validates offending off-store extension verdicts received in a telemetry
  // report response, and converts them into a blocklist state map for the
  // ExtensionService to act on.
  void ProcessOffstoreExtensionVerdicts(
      const ExtensionTelemetryReportResponse& response);

  // Common variables shared between ESB and enterprise reporting:
  // The profile with which this instance of the service is associated.
  const raw_ptr<Profile> profile_;

  // Unowned objects used for getting extension information.
  const raw_ptr<extensions::ExtensionRegistry> extension_registry_;
  const raw_ptr<extensions::ExtensionPrefs> extension_prefs_;

  // The |file_processor_| object reads and hashes offstore extension files.
  // Since these are blocking operations, it is bound to a different sequence
  // task runner.
  base::SequenceBound<ExtensionTelemetryFileProcessor> file_processor_;

  // Stores extension objects for extensions that are included in the
  // --load-extension command line switch.
  extensions::ExtensionSet commandline_extensions_;
  // Used to ensure that the information about command line extensions is only
  // collected once.
  bool collected_commandline_extension_info_ = false;

  // Maps offstore extension id to extension root path
  using OffstoreExtensionDirs =
      base::flat_map<extensions::ExtensionId, base::FilePath>;
  OffstoreExtensionDirs offstore_extension_dirs_;
  // Set of offstore extensions to process in order. Sorted by oldest last
  // processing time.
  base::flat_set<OffstoreExtensionFileDataContext>
      offstore_extension_file_data_contexts_;
  // Used to start the initial offstore extension file data collection based on
  // |kOffstoreFileDataCollectionStartupDelaySeconds| - default: 5 mins.
  // Then repeat the collection based on
  // |kOffstoreFileDataCollectionIntervalSeconds| - default: 2 hours.
  base::OneShotTimer offstore_file_data_collection_timer_;
  base::TimeTicks offstore_file_data_collection_start_time_;
  base::TimeDelta offstore_file_data_collection_duration_limit_;

  // ESB-specific reporting variables:
  // Keeps track of the state of the service for ESB telemetry reporting.
  bool esb_enabled_ = false;

  SignalProcessors signal_processors_;
  SignalSubscribers signal_subscribers_;

  // Stores data on extensions with generated signals for ESB reporting.
  ExtensionStore extension_store_;

  // Used for periodic collection of ESB telemetry reports.
  base::RepeatingTimer timer_;
  base::TimeDelta current_reporting_interval_;

  // The persister object is bound to the threadpool. This prevents the
  // the read/write operations the `persister_` runs from blocking
  // the UI thread. It also allows the `persister_` object to be
  // destroyed cleanly while running tasks during Chrome shutdown.
  base::SequenceBound<ExtensionTelemetryPersister> persister_;

  // The `config_manager_` manages all configurable variables of the
  // Extension Telemetry Service. Variables are stored in Chrome Prefs
  // between sessions.
  std::unique_ptr<safe_browsing::ExtensionTelemetryConfigManager>
      config_manager_;

  // The URLLoaderFactory used to issue network requests.
  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;

  // Unowned object used for getting preference settings.
  raw_ptr<PrefService> pref_service_;

  // Observes changes to kSafeBrowsingEnhanced.
  PrefChangeRegistrar pref_change_registrar_;

  // Specifies the number of times(N) the telemetry service checks if a
  // telemetry upload is required within an upload interval(I). The telemetry
  // service checks if an upload is necessary at I/N intervals. At each check
  // interval, the in-memory telemetry data is saved to disk - till the time an
  // upload interval has elapsed. For example, a value of 2 means that the
  // telemetry service checks for uploads at I/2 and I. At the first check
  // interval, the in-memory report is written to disk. At the second check
  // interval, the in-memory report and the previously saved report in disk are
  // both uploaded to the telemetry server.
  int num_checks_per_upload_interval_;

  // The current report being uploaded.
  std::unique_ptr<ExtensionTelemetryReportRequest> active_report_;
  // The current uploader instance uploading the active report.
  std::unique_ptr<ExtensionTelemetryUploader> active_uploader_;

  // Generates a potential search hijacking signal.
  std::unique_ptr<SearchHijackingDetector> search_hijacking_detector_;

  // Ingests extension activity log events and creates telemetry signals.
  std::unique_ptr<ActivityLogIngester> activity_log_ingester_;

  // Enterprise-specific reporting variables:
  // Keeps track of the state of the service for enterprise telemetry reporting.
  bool enterprise_enabled_ = false;

  SignalProcessors enterprise_signal_processors_;
  SignalSubscribers enterprise_signal_subscribers_;

  // Stores data on extensions with generated signals for enterprise reporting.
  ExtensionStore enterprise_extension_store_;

  // Used for periodic collection of enterprise telemetry reports.
  base::RepeatingTimer enterprise_timer_;

  // Indicates whether |Shutdown| has been called.
  bool is_shutdown_ = false;

  friend class ExtensionTelemetryServiceTest;
  friend class ExtensionTelemetryServiceBrowserTest;
  FRIEND_TEST_ALL_PREFIXES(ExtensionTelemetryServiceTest,
                           PersistsReportsOnInterval);
  FRIEND_TEST_ALL_PREFIXES(ExtensionTelemetryServiceTest,
                           MalformedPersistedFile);
  FRIEND_TEST_ALL_PREFIXES(ExtensionTelemetryServiceTest,
                           FileData_EnforcesCollectionDurationLimit);

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

}  // namespace safe_browsing

#endif  // CHROME_BROWSER_SAFE_BROWSING_EXTENSION_TELEMETRY_EXTENSION_TELEMETRY_SERVICE_H_
