// 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_CHROMEOS_POLICY_DLP_DLP_CONTENT_MANAGER_H_
#define CHROME_BROWSER_CHROMEOS_POLICY_DLP_DLP_CONTENT_MANAGER_H_
#include <memory>
#include <string>
#include <utility>
#include "base/containers/flat_map.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/policy/dlp/dialogs/dlp_warn_dialog.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_content_manager_observer.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_content_observer.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_content_restriction_set.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_content_tab_helper.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "content/public/browser/desktop_media_id.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/media_stream_request.h"
#include "ui/views/widget/widget.h"
#include "url/gurl.h"
namespace content {
struct DesktopMediaID;
class WebContents;
} // namespace content
namespace data_controls {
class DlpReportingManager;
} // namespace data_controls
namespace policy {
class DlpWarnNotifier;
// System-wide class that tracks the set of currently known confidential
// WebContents and whether any of them are currently visible.
// If any confidential WebContents is visible, the corresponding restrictions
// will be enforced according to the current enterprise policy.
class DlpContentManager : public DlpContentObserver,
public BrowserListObserver,
public TabStripModelObserver {
public:
// Holds DLP restrictions information for `web_contents` object.
struct WebContentsInfo {
WebContentsInfo();
WebContentsInfo(content::WebContents* web_contents,
DlpContentRestrictionSet restriction_set,
std::vector<DlpContentTabHelper::RfhInfo> rfh_info_vector);
WebContentsInfo(const WebContentsInfo&);
WebContentsInfo& operator=(const WebContentsInfo&);
~WebContentsInfo();
raw_ptr<content::WebContents> web_contents = nullptr;
// Restrictions set for `web_contents`.
DlpContentRestrictionSet restriction_set;
// DLP restrictions info for RenderFrameHosts in `web_contents`.
std::vector<DlpContentTabHelper::RfhInfo> rfh_info_vector;
};
DlpContentManager(const DlpContentManager&) = delete;
DlpContentManager& operator=(const DlpContentManager&) = delete;
// Returns platform-specific implementation of the class. Never returns
// nullptr.
static DlpContentManager* Get();
// Returns which restrictions are applied to the |web_contents| according to
// the policy.
DlpContentRestrictionSet GetConfidentialRestrictions(
content::WebContents* web_contents) const;
// Returns whether screenshare should be blocked for the specified
// WebContents.
bool IsScreenShareBlocked(content::WebContents* web_contents) const;
// Checks whether printing of |web_contents| is restricted or not advised.
// Depending on the result, calls |callback| and passes an indicator whether
// to proceed or not.
void CheckPrintingRestriction(content::WebContents* web_contents,
content::GlobalRenderFrameHostId rfh_id,
WarningCallback callback);
// Returns whether screenshots should be restricted for extensions API.
virtual bool IsScreenshotApiRestricted(content::WebContents* web_contents);
// Checks whether screen sharing of content from the |media_id| source with
// application |application_title| is restricted or not advised. Depending on
// the result, calls |callback| and passes an indicator whether to proceed or
// not.
virtual void CheckScreenShareRestriction(
const content::DesktopMediaID& media_id,
const std::u16string& application_title,
WarningCallback callback) = 0;
// Called when screen share is started.
// |state_change_callback| will be called when restricted content will appear
// or disappear in the captured area to pause/resume the share.
// |source_callback| will be called only to update the source for a tab share
// before resuming the capture.
// |stop_callback| will be called after a user dismisses a warning.
virtual void OnScreenShareStarted(
const std::string& label,
std::vector<content::DesktopMediaID> screen_share_ids,
const std::u16string& application_title,
base::RepeatingClosure stop_callback,
content::MediaStreamUI::StateChangeCallback state_change_callback,
content::MediaStreamUI::SourceCallback source_callback) = 0;
// Called when screen share is stopped.
virtual void OnScreenShareStopped(
const std::string& label,
const content::DesktopMediaID& media_id) = 0;
// Called when a screen share is being stopped before processing a source
// change from |old_media_id| to |new_media_id|. The share might have been
// paused by DLP due to restricted content, so should be resumed before the
// change source request proceeds.
virtual void OnScreenShareSourceChanging(
const std::string& label,
const content::DesktopMediaID& old_media_id,
const content::DesktopMediaID& new_media_id,
bool captured_surface_control_active);
void AddObserver(DlpContentManagerObserver* observer,
DlpContentRestriction restriction);
void RemoveObserver(const DlpContentManagerObserver* observer,
DlpContentRestriction restriction);
// Returns an array of DLP restrictions info to all the tracked WebContents.
std::vector<WebContentsInfo> GetWebContentsInfo() const;
protected:
friend class DlpContentManagerTestHelper;
void SetReportingManagerForTesting(
data_controls::DlpReportingManager* manager);
void SetWarnNotifierForTesting(
std::unique_ptr<DlpWarnNotifier> warn_notifier);
void ResetWarnNotifierForTesting();
// Sets the delay before resuming a screen share.
static void SetScreenShareResumeDelayForTesting(base::TimeDelta delay);
// Structure that relates a list of confidential contents to the
// corresponding restriction level.
struct ConfidentialContentsInfo {
RestrictionLevelAndUrl restriction_info;
DlpConfidentialContents confidential_contents;
};
// Used to keep track of running screen shares.
class ScreenShareInfo {
public:
enum class State {
kRunning,
kPaused,
kStopped,
kRunningBeforeSourceChange,
kPausedBeforeSourceChange
};
ScreenShareInfo(
const std::string& label,
const content::DesktopMediaID& media_id,
const std::u16string& application_title,
base::OnceClosure stop_callback,
content::MediaStreamUI::StateChangeCallback state_change_callback,
content::MediaStreamUI::SourceCallback source_callback);
~ScreenShareInfo();
// Updates an existing ScreenShareInfo instance. Should only be called for
// tab shares and after a source change has occurred. In addition to the
// passed parameters, restores the state to a correct value and updates
// notifications if necessary.
void UpdateAfterSourceChange(
const content::DesktopMediaID& media_id,
const std::u16string& application_title,
base::OnceClosure stop_callback,
content::MediaStreamUI::StateChangeCallback state_change_callback,
content::MediaStreamUI::SourceCallback source_callback);
bool operator==(const ScreenShareInfo& other) const;
bool operator!=(const ScreenShareInfo& other) const;
const content::DesktopMediaID& media_id() const;
const content::DesktopMediaID& new_media_id() const;
void set_new_media_id(const content::DesktopMediaID& media_id);
const std::string& label() const;
const std::u16string& application_title() const;
State state() const;
base::WeakPtr<content::WebContents> web_contents() const;
// Saves the |dialog_widget| as the current dialog handle.
// Assumes that the previous widget is closed or not set. It's
// responsibility of the called to ensure that the restriction level is
// WARN.
void set_dialog_widget(base::WeakPtr<views::Widget> dialog_widget);
void set_latest_confidential_contents_info(
ConfidentialContentsInfo confidential_contents_info);
// Returns the restriction information that was the last enforced on this
// screen share.
const RestrictionLevelAndUrl& GetLatestRestriction() const;
// Returns the confidential contents that caused the latest restriction.
const DlpConfidentialContents& GetConfidentialContents() const;
// Pauses a running screen share.
// No-op if the screen share is already paused.
void Pause();
// Resumes a paused screen share.
// For tab shares, resuming can be a consequence of navigating within a page
// so calls the |source_callback_| to ensure that the |media_id| is updated.
// No-op if the screen share is already running.
void Resume();
// Changes the state to indicate that the source is being changed.
// Every source change stops the share and starts a new one, so this is
// needed to store all the required information in the meantime and update
// it if the source change is successful.
void ChangeStateBeforeSourceChange();
// Stops the screen share. Can only be called once.
void Stop();
// Start the screen share after source change if pending.
void StartIfPending();
// If necessary, hides or shows the paused/resumed notification for this
// screen share. The notification should be updated after changing the state
// from running to paused, or paused to running.
void MaybeUpdateNotifications();
// If shown, hides both the paused and resumed notification for this screen
// share.
void HideNotifications();
// If currently opened, closes the associated DlpWarnDialog widget.
void MaybeCloseDialogWidget();
// Returns true if there is an associated DlpWarnDialog object, false
// otherwise.
bool HasOpenDialogWidget();
// Records that Captured Surface Control was active at some point during
// the capture.
void SetCapturedSurfaceControlActive();
base::WeakPtr<ScreenShareInfo> GetWeakPtr();
private:
enum class NotificationState {
kNotShowingNotification,
kShowingPausedNotification,
kShowingResumedNotification
};
// Shows (if |show| is true) or hides (if |show| is false) paused
// notification for this screen share. Does nothing if the notification is
// already in the required state.
void UpdatePausedNotification(bool show);
// Shows (if |show| is true) or hides (if |show| is false) resumed
// notification for this screen share. Does nothing if the notification is
// already in the required state.
void UpdateResumedNotification(bool show);
std::string label_;
content::DesktopMediaID media_id_;
content::DesktopMediaID new_media_id_;
std::u16string application_title_;
base::OnceClosure stop_callback_;
content::MediaStreamUI::StateChangeCallback state_change_callback_;
content::MediaStreamUI::SourceCallback source_callback_;
State state_ = State::kRunning;
NotificationState notification_state_ =
NotificationState::kNotShowingNotification;
// Information on the latest restriction enforced.
ConfidentialContentsInfo latest_confidential_contents_info_;
// Pointer to the associated DlpWarnDialog widget.
// Not null only while the dialog is opened.
base::WeakPtr<views::Widget> dialog_widget_ = nullptr;
// Remembers that it should be restarted after source update.
bool pending_start_on_source_change_ = false;
// Set only for tab shares.
base::WeakPtr<content::WebContents> web_contents_;
// Only meaningful for tab-shares.
bool captured_surface_control_active_ = false;
base::WeakPtrFactory<ScreenShareInfo> weak_factory_{this};
};
DlpContentManager();
~DlpContentManager() override;
// Reports events of type DlpPolicyEvent::Mode::WARN_PROCEED to
// `reporting_manager`.
static void ReportWarningProceededEvent(
const GURL& url,
DlpRulesManager::Restriction restriction,
data_controls::DlpReportingManager* reporting_manager);
// Helper method to create a callback with ReportWarningProceededEvent
// function.
static bool MaybeReportWarningProceededEvent(
GURL url,
DlpRulesManager::Restriction restriction,
data_controls::DlpReportingManager* reporting_manager,
bool should_proceed);
// Retrieves WebContents from |media_id| for tab shares. Otherwise returns
// nullptr.
static content::WebContents* GetWebContentsFromMediaId(
const content::DesktopMediaID& media_id);
// Initializing to be called separately to make testing possible.
virtual void Init();
// DlpContentObserver overrides:
void OnConfidentialityChanged(
content::WebContents* web_contents,
const DlpContentRestrictionSet& restriction_set) override;
void OnWebContentsDestroyed(content::WebContents* web_contents) override;
// BrowserListObserver overrides:
void OnBrowserAdded(Browser* browser) override;
void OnBrowserRemoved(Browser* browser) override;
// TabStripModelObserver overrides:
void OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) override;
// Called when tab was probably moved, but without change of the visibility.
virtual void TabLocationMaybeChanged(content::WebContents* web_contents) = 0;
// Helper to remove |web_contents| from the confidential set.
virtual void RemoveFromConfidential(content::WebContents* web_contents);
// Returns which level and url of printing restriction is currently enforced
// for |web_contents|.
RestrictionLevelAndUrl GetPrintingRestrictionInfo(
content::WebContents* web_contents,
content::GlobalRenderFrameHostId rfh_id) const;
// Returns confidential info for screen share of a single |web_contents|.
ConfidentialContentsInfo GetScreenShareConfidentialContentsInfoForWebContents(
content::WebContents* web_contents) const;
// Applies retrieved restrictions in |info| to screens share attempt from
// app |application_title| and calls the |callback| with a result.
void ProcessScreenShareRestriction(const std::u16string& application_title,
ConfidentialContentsInfo info,
WarningCallback callback);
// Returns which level, url, and information about visible confidential
// contents of screen share restriction that is currently enforced for
// |media_id|. |web_contents| is not null for tab shares.
virtual ConfidentialContentsInfo GetScreenShareConfidentialContentsInfo(
const content::DesktopMediaID& media_id,
content::WebContents* web_contents) const = 0;
// If a screen share with the same |label| already exists in
// |running_screen_shares_|, updates the existing object. Otherwise adds a new
// screen share to be tracked in |running_screen_shares_|. Callbacks are used
// to control the screen share state in case it should be paused, resumed or
// completely stopped by DLP.
void AddOrUpdateScreenShare(
const std::string& label,
const content::DesktopMediaID& media_id,
const std::u16string& application_title,
base::RepeatingClosure stop_callback,
content::MediaStreamUI::StateChangeCallback state_change_callback,
content::MediaStreamUI::SourceCallback source_callback);
// Removes screen share from |running_screen_shares_|.
void RemoveScreenShare(const std::string& label,
const content::DesktopMediaID& media_id);
// Checks and stops the running screen shares if restricted content appeared
// in the corresponding areas.
void CheckRunningScreenShares();
// Resumes the |screen_share| after a delay if it's still necessary.
void MaybeResumeScreenShare(base::WeakPtr<ScreenShareInfo> screen_share);
// Called back from Screen Share warning dialogs that are shown during the
// screen share. Passes along the user's response, reflected in the value of
// |should_proceed| along to |callback| which handles continuing or cancelling
// the action based on this response. In case that |should_proceed| is true,
// also saves the |confidential_contents| that were allowed to be shared by
// the user to avoid future warnings.
void OnDlpScreenShareWarnDialogReply(
const ConfidentialContentsInfo& info,
base::WeakPtr<ScreenShareInfo> screen_share,
bool should_proceed);
// Called back from warning dialogs. Passes along the user's response,
// reflected in the value of |should_proceed| along to |callback| which
// handles continuing or cancelling the action based on this response. In case
// that |should_proceed| is true, also saves the |confidential_contents| that
// were allowed by the user for |restriction| to avoid future warnings.
void OnDlpWarnDialogReply(
const DlpConfidentialContents& confidential_contents,
DlpRulesManager::Restriction restriction,
WarningCallback callback,
bool should_proceed);
// Reports events if required by the |restriction_info| and
// `reporting_manager` is configured.
void MaybeReportEvent(const RestrictionLevelAndUrl& restriction_info,
DlpRulesManager::Restriction restriction);
// Reports warning events if `reporting_manager` is configured.
void ReportWarningEvent(const GURL& url,
DlpRulesManager::Restriction restriction);
// Removes all elemxents of |contents| that the user has recently already
// acknowledged the warning for.
void RemoveAllowedContents(DlpConfidentialContents& contents,
DlpRulesManager::Restriction restriction);
// Updates confidentiality for |web_contents| with the |restriction_set|.
void UpdateConfidentiality(content::WebContents* web_contents,
const DlpContentRestrictionSet& restriction_set);
// Notifies observers if the restrictions they are listening to changed.
void NotifyOnConfidentialityChanged(
const DlpContentRestrictionSet& old_restriction_set,
const DlpContentRestrictionSet& new_restriction_set,
content::WebContents* web_contents);
// Map from currently known confidential WebContents to the restrictions.
base::flat_map<content::WebContents*, DlpContentRestrictionSet>
confidential_web_contents_;
// Keeps track of the contents for which the user allowed the action after
// being shown a warning for each type of restriction.
// TODO(crbug.com/1264803): Change to DlpConfidentialContentsCache
DlpConfidentialContentsCache user_allowed_contents_cache_;
// List of the currently running screen shares.
std::vector<std::unique_ptr<ScreenShareInfo>> running_screen_shares_;
raw_ptr<data_controls::DlpReportingManager, DanglingUntriaged>
reporting_manager_{nullptr};
std::unique_ptr<DlpWarnNotifier> warn_notifier_;
// One ObserverList per restriction.
std::array<base::ObserverList<DlpContentManagerObserver>,
static_cast<int>(DlpContentRestriction::kMaxValue) + 1>
observer_lists_;
// A helper structure that contains web contents which were reported during
// the current screen share.
// Navigating a tab or switching a tab with share-this-tab-instead does not
// invalidate this contents.
struct LastReportedScreenShare {
// Checks if DLP should report for |label| and |confidential_contents|. If
// yes, then updates internal structures. Does not emit any reporting event.
bool ShouldReportAndUpdate(
const std::string& label,
const DlpConfidentialContents& confidential_contents);
private:
std::string label_;
DlpConfidentialContents confidential_contents_;
} last_reported_screen_share_;
};
} // namespace policy
#endif // CHROME_BROWSER_CHROMEOS_POLICY_DLP_DLP_CONTENT_MANAGER_H_