chromium/chrome/browser/ash/policy/dlp/files_policy_notification_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 CHROME_BROWSER_ASH_POLICY_DLP_FILES_POLICY_NOTIFICATION_MANAGER_H_
#define CHROME_BROWSER_ASH_POLICY_DLP_FILES_POLICY_NOTIFICATION_MANAGER_H_

#include <memory>
#include <optional>
#include <queue>

#include "base/files/file_path.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/ash/file_manager/io_task.h"
#include "chrome/browser/ash/file_manager/io_task_controller.h"
#include "chrome/browser/ash/policy/dlp/dialogs/files_policy_dialog.h"
#include "chrome/browser/chromeos/policy/dlp/dialogs/policy_dialog_base.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_confidential_file.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_file_destination.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_files_utils.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "components/keyed_service/core/keyed_service.h"
#include "content/public/browser/browser_context.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/views/widget/widget_observer.h"

namespace content {
class BrowserContext;
}  // namespace content

namespace policy {

// The type of policy notification.
enum class NotificationType {
  kError,
  kWarning,
};

// The policy notification button index.
enum NotificationButton {
  CANCEL = 0,
  OK,
};

// FilesPolicyNotificationManager is responsible for showing block and warning
// notifications/dialogs for files because of DLP and enterprise connectors
// policies.
class FilesPolicyNotificationManager
    : public KeyedService,
      public BrowserListObserver,
      public file_manager::io_task::IOTaskController::Observer {
 public:
  explicit FilesPolicyNotificationManager(content::BrowserContext* context);

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

  ~FilesPolicyNotificationManager() override;

  // KeyedService overrides:
  void Shutdown() override;

  // Show DLP block UI. If `task_id` is set, the corresponding IOTask will be
  // updated with the blocked files. Otherwise a desktop notification will be
  // shown.
  virtual void ShowDlpBlockedFiles(
      std::optional<file_manager::io_task::IOTaskId> task_id,
      std::vector<base::FilePath> blocked_files,
      dlp::FileAction action);

  // Associates `dialog_info` to the given enterprise connectors `reason` in the
  // information corresponding to the IOTask with `task_id`. This will replace
  // previously stored dialog info with the same `reason`.
  virtual void SetConnectorsBlockedFiles(
      file_manager::io_task::IOTaskId task_id,
      dlp::FileAction action,
      FilesPolicyDialog::BlockReason reason,
      FilesPolicyDialog::Info dialog_info);

  // Shows DLP Warning UI. If `task_id` is set, the corresponding IOTask
  // will be paused. Otherwise a desktop notification will be shown. Virtual
  // to allow overrides in tests.
  virtual void ShowDlpWarning(
      WarningWithJustificationCallback callback,
      std::optional<file_manager::io_task::IOTaskId> task_id,
      std::vector<base::FilePath> warning_files,
      const DlpFileDestination& destination,
      dlp::FileAction action);

  // Shows Connectors Warning UI and pauses the corresponding IOTask.
  // Virtual to allow overrides in tests.
  virtual void ShowConnectorsWarning(WarningWithJustificationCallback callback,
                                     file_manager::io_task::IOTaskId task_id,
                                     dlp::FileAction action,
                                     FilesPolicyDialog::Info dialog_info);

  // Shows a Files Policy warning or error desktop notification with
  // `notification_id` based on `status`. Used for IO tasks.
  virtual void ShowFilesPolicyNotification(
      const std::string& notification_id,
      const file_manager::io_task::ProgressStatus& status);

  // Shows a policy dialog of type `type` for task identified by `task_id`.
  // Used for copy and move operations.
  virtual void ShowDialog(file_manager::io_task::IOTaskId task_id,
                          FilesDialogType type);

  // Shows a DLP warning timeout notification for `action`. `notification_id`
  // should have value for IO tasks. When it  doesn't have a value, i.e. for non
  // IO tasks, computes a new unique id for the notification.
  void ShowDlpWarningTimeoutNotification(
      dlp::FileAction action,
      std::optional<std::string> notification_id = std::nullopt);

  // Returns whether IO task is being tracked.
  bool HasIOTask(file_manager::io_task::IOTaskId task_id) const;

  // Runs warning callback for the corresponding IOTask with should_proceed set
  // to true.
  virtual void OnIOTaskResumed(file_manager::io_task::IOTaskId task_id);

  // Force shows a desktop notification for all tracked IO tasks with blocked
  // files.
  void ShowBlockedNotifications();

  // Clears any info stored about the task with `task_id`.
  virtual void OnErrorItemDismissed(file_manager::io_task::IOTaskId task_id);

  std::map<FilesPolicyDialog::BlockReason, FilesPolicyDialog::Info>
  GetIOTaskDialogInfoMapForTesting(
      file_manager::io_task::IOTaskId task_id) const;
  // Returns whether IO task has a warning timeout timer.
  bool HasWarningTimerForTesting(file_manager::io_task::IOTaskId task_id) const;

  // Used in tests to set the test task runner.
  void SetTaskRunnerForTesting(scoped_refptr<base::SequencedTaskRunner>);

 protected:
  // The number of notifications shown so far. Used to calculate a unique
  // notification ID. Only applies to non IOTasks operations (upload, download,
  // etc.) as notifications for IOTasks are shown based on the task state from
  // the SystemNotificationManager.
  size_t notification_count_ = 0;

 private:
  // Holds all information related to file task warning. Any extra information
  // needed for custom messaging should be added here.
  struct WarningInfo {
    WarningInfo() = delete;
    WarningInfo(Policy warning_reason,
                WarningWithJustificationCallback warning_callback,
                WarningWithJustificationCallback dialog_callback,
                FilesPolicyDialog::Info dialog_info);
    WarningInfo(WarningInfo&& other);
    ~WarningInfo();

    // Warning reason. There should be only one policy per warning as mixed
    // warnings aren't supported.
    Policy warning_reason;
    // Warning callback.
    WarningWithJustificationCallback warning_callback;
    // Invoked by clicking on dialog's buttons. Wrapper around `callback` as it
    // performs additional actions before running `callback` with the same
    // `should_proceed` parameter.
    WarningWithJustificationCallback dialog_callback;
    // Holds warning dialog info such as the warned files, the warning message,
    // an optional custom learn more URL or whether bypassing the warning
    // requires a user justification that should be used when displaying the
    // dialog.
    FilesPolicyDialog::Info dialog_info;

    // A potentially saved justification for bypassing a warning.
    std::optional<std::u16string> user_justification;
  };

  // Holds needed information for each tracked file task.
  class FileTaskInfo : public views::WidgetObserver {
   public:
    explicit FileTaskInfo(dlp::FileAction action);
    FileTaskInfo(FileTaskInfo&& other);
    ~FileTaskInfo() override;

    // Starts observing `widget`. Should be called when the warning/error dialog
    // is created.
    void AddWidget(views::Widget* widget);
    // Closes `widget_` if it's not nullptr.
    void CloseWidget();
    views::Widget* widget() const { return widget_; }

    // Sets `warning_info_`.
    void SetWarningInfo(WarningInfo warning_info);
    // Resets `warning_info_`.
    void ResetWarningInfo();
    // Returns a pointer to WarningInfo if it exists. Otherwise, it returns
    // nullptr.
    WarningInfo* GetWarningInfo();
    // Returns true if `warning_info_` has value.
    bool HasWarningInfo() const;

    const std::map<FilesPolicyDialog::BlockReason, FilesPolicyDialog::Info>&
    block_info_map() const {
      return block_info_map_;
    }

    // Stores `dialog_info` associated with the given `reason`.
    // Calling this method a second time with the same `reason` will overwrite
    // previously stored `files` and `dialog_settings`.
    void SetBlockedFiles(FilesPolicyDialog::BlockReason reason,
                         FilesPolicyDialog::Info dialog_info);

    // Returns the overall number of blocked files irrespective of their block
    // reason.
    size_t GetBlockedFilesSize() const;

    dlp::FileAction action() const { return action_; }

   private:
    // views::WidgetObserver overrides:
    void OnWidgetDestroying(views::Widget* widget) override;

    // Should have value only if there's warning.
    std::optional<WarningInfo> warning_info_;
    // A map of files and dialog settings blocked for certain block reasons.
    std::map<FilesPolicyDialog::BlockReason, FilesPolicyDialog::Info>
        block_info_map_;
    // The action that's restricted.
    dlp::FileAction action_;
    // Warning/Error dialog widget. Each FileTask is expected to have only one
    // open dialog at a time.
    raw_ptr<views::Widget> widget_ = nullptr;
    // Warning/Error dialog widget observation.
    base::ScopedObservation<views::Widget, views::WidgetObserver>
        widget_observation_{this};
  };

  // Callback to show the dialog. Invoked with a Files App window when
  // successfully opened, or null if opening the Files App times out.
  using ShowDialogCallback = base::OnceCallback<void(gfx::NativeWindow)>;

  // Holds information for showing a Files Policy dialog.
  struct DialogInfo {
    DialogInfo() = delete;
    DialogInfo(ShowDialogCallback callback,
               file_manager::io_task::IOTaskId task_id,
               base::OnceClosure timeout_callback);
    DialogInfo(ShowDialogCallback callback,
               std::string notification_id,
               base::OnceClosure timeout_callback);
    ~DialogInfo();

    // Id of the task for which dialog is being shown. Used for Copy and Move
    // IOTasks.
    std::optional<file_manager::io_task::IOTaskId> task_id;
    // Id of the notification for which dialog is being shown. Used for non IO
    // tasks.
    std::optional<std::string> notification_id;
    // Callback to show the dialog.
    ShowDialogCallback dialog_callback;
    // Callback to stop waiting for the Files app.
    base::OnceClosure timeout_callback;
    base::TimeTicks created_at;
    base::OneShotTimer timeout_timer;
  };

  // Shows a Files Policy warning or error desktop notification with
  // `notification_id` for an IOTask with `task_id`.
  void ShowFilesPolicyNotification(const std::string& notification_id,
                                   file_manager::io_task::IOTaskId task_id);

  // Click handler for Data Leak Prevention or Enterprise Connectors policy
  // warning notifications.
  void HandleFilesPolicyWarningNotificationClick(
      file_manager::io_task::IOTaskId task_id,
      std::string notification_id,
      std::optional<int> button_index);

  // Click handler for Data Leak Prevention or Enterprise Connectors policy
  // error notifications.
  void HandleFilesPolicyErrorNotificationClick(
      file_manager::io_task::IOTaskId task_id,
      std::string notification_id,
      std::optional<int> button_index);

  // Click handler for DLP warning notifications. Used for non IO tasks.
  void HandleDlpWarningNotificationClick(std::string notification_id,
                                         std::optional<int> button_index);

  // Click handler for DLP error notifications. Used for non IO tasks.
  void HandleDlpErrorNotificationClick(std::string notification_id,
                                       std::vector<base::FilePath> files,
                                       dlp::FileAction action,
                                       std::optional<int> button_index);

  // Shows a FilesPolicyDialog of `type` for task with `task_id`.
  void ShowDialogForIOTask(file_manager::io_task::IOTaskId task_id,
                           FilesDialogType type,
                           gfx::NativeWindow modal_parent);

  // Shows a FilesPolicyDialog of `type` for non-IO task associated with
  // `notification_id`.
  void ShowDialogForNonIOTask(std::string notification_id,
                              FilesDialogType type,
                              gfx::NativeWindow modal_parent);

  // Shows a FilesPolicyDialog of `type` based on `info`.
  void ShowFilesPolicyDialog(FileTaskInfo& info,
                             FilesDialogType type,
                             gfx::NativeWindow modal_parent);

  // Starts tracking IO task with `task_id`.
  void AddIOTask(file_manager::io_task::IOTaskId task_id,
                 dlp::FileAction action);

  // Launches the Files App in default directory and appends `dialog_info` to
  // the queue of pending dialogs in order to show the dialog over it.
  void LaunchFilesApp(std::unique_ptr<DialogInfo> dialog_info);

  // BrowserListObserver overrides:
  // Called when opening a new Files App window to use as the modal parent for a
  // FilesPolicyDialog.
  void OnBrowserAdded(Browser* browser) override;

  // file_manager::io_task::IOTaskController::Observer overrides:
  void OnIOTaskStatus(
      const file_manager::io_task::ProgressStatus& status) override;

  // Returns whether IO task has any blocked file.
  bool HasBlockedFiles(file_manager::io_task::IOTaskId task_id) const;

  // Returns whether IO task has a warning.
  bool HasWarning(file_manager::io_task::IOTaskId task_id) const;

  // Returns whether non IO task is being tracked.
  bool HasNonIOTask(const std::string notification_id) const;

  // Returns whether non IO task has any blocked file.
  bool HasBlockedFiles(const std::string notification_id) const;

  // Returns whether non IO task has a warning.
  bool HasWarning(const std::string notification_id) const;

  // Called when the user clicks on one of the warning dialog's buttons.
  // Resumes/cancels the task with `task_id` based on the value of
  // `should_proceed`. Used for Copy and Move IOTasks.
  void OnIOTaskWarningDialogClicked(
      file_manager::io_task::IOTaskId task_id,
      Policy warning_reason,
      std::optional<std::u16string> user_justification,
      bool should_proceed);

  // Called when the user clicks on one of the warning dialog's buttons.
  // associated with `notification_id`. Resumes/cancels the operation based on
  // the value of `should_proceed`.
  void OnNonIOTaskWarningDialogClicked(
      const std::string& notification_id,
      std::optional<std::u16string> user_justification,
      bool should_proceed);

  // Opens DLP Learn more link and closes the notification having
  // `notification_id`.
  void OnDlpLearnMoreButtonClicked(const std::string& notification_id,
                                   std::optional<int> button_index);

  // Calls the IOTaskController to resume the task with `task_id`.
  void Resume(file_manager::io_task::IOTaskId task_id);

  // Calls the IOTaskController to cancel the task with `task_id`.
  void Cancel(file_manager::io_task::IOTaskId task_id);

  // Shows DLP block desktop notification.
  void ShowDlpBlockNotification(std::vector<base::FilePath> blocked_files,
                                dlp::FileAction action);

  // Shows DLP warning desktop notification.
  void ShowDlpWarningNotification(WarningWithJustificationCallback callback,
                                  std::vector<base::FilePath> warning_files,
                                  const DlpFileDestination& destination,
                                  dlp::FileAction action);

  // Pauses IO task due to `warning_reason`.
  void PauseIOTask(file_manager::io_task::IOTaskId task_id,
                   WarningWithJustificationCallback callback,
                   dlp::FileAction action,
                   Policy warning_reason,
                   FilesPolicyDialog::Info dialog_info);

  // Called after opening the Files App times out.
  // Stops waiting for the app and shows a dialog for `task_id` without a modal
  // parent (i.e. as a system modal).
  void OnIOTaskAppLaunchTimedOut(file_manager::io_task::IOTaskId task_id);

  // Called after opening the Files App times out.
  // Stops waiting for the app and shows a dialog for `notification_id` without
  // a modal parent (i.e. as a system modal).
  void OnNonIOTaskAppLaunchTimedOut(std::string notification_id);

  // Helper method that pops the oldest entry from `pending_dialogs_` and
  // creates a dialog with with `modal_parent`. No-op if the list is empty.
  void ShowPendingDialog(gfx::NativeWindow modal_parent);

  // Called when the warning times out. Stops waiting for the user input,
  // cancels the task, and runs the warning callback with should_proceed set to
  // false.
  void OnIOTaskWarningTimedOut(const file_manager::io_task::IOTaskId& task_id);

  // Called when the warning times out. Stops waiting for the user input, and
  // runs the warning callback with should_proceed set to false.
  void OnNonIOTaskWarningTimedOut(const std::string& notification_id);

  // Callback to show a policy dialog after waiting to open a Files App window.
  base::OnceCallback<void(gfx::NativeWindow)> pending_callback_;

  // Context for which the FPNM is created.
  raw_ptr<content::BrowserContext, DanglingUntriaged> context_;

  // A map from tracked IO tasks ids to their info.
  std::map<file_manager::io_task::IOTaskId, FileTaskInfo> io_tasks_;

  // A map from notification ids to related task info for non IO operations.
  std::map<std::string, FileTaskInfo> non_io_tasks_;

  // Callbacks to show a policy dialog after waiting to open a Files App window.
  std::queue<std::unique_ptr<DialogInfo>> pending_dialogs_;

  // Used to fallack to system modal if opening the Files App times out.
  scoped_refptr<base::SequencedTaskRunner> task_runner_;

  // Active timers for IOTasks warnings.
  base::flat_map<file_manager::io_task::IOTaskId,
                 std::unique_ptr<base::OneShotTimer>>
      io_tasks_warning_timers_;

  // Active timers for non-IOTasks warnings.
  base::flat_map<std::string, std::unique_ptr<base::OneShotTimer>>
      non_io_tasks_warning_timers_;

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

}  // namespace policy

#endif  // CHROME_BROWSER_ASH_POLICY_DLP_FILES_POLICY_NOTIFICATION_MANAGER_H_