chromium/chrome/browser/chromeos/policy/dlp/data_transfer_dlp_controller_browsertest.cc

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

#include "chrome/browser/chromeos/policy/dlp/data_transfer_dlp_controller.h"

#include <memory>
#include <string>
#include <string_view>

#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/json/json_writer.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/types/optional_util.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_policy_constants.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_factory.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_impl.h"
#include "chrome/browser/chromeos/policy/dlp/test/dlp_rules_manager_test_utils.h"
#include "chrome/browser/enterprise/data_controls/dlp_reporting_manager.h"
#include "chrome/browser/enterprise/data_controls/dlp_reporting_manager_test_helper.h"
#include "chrome/browser/policy/messaging_layer/public/report_client_test_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/enterprise/data_controls/core/browser/dlp_histogram_helper.h"
#include "components/enterprise/data_controls/core/browser/dlp_policy_event.pb.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/policy/policy_constants.h"
#include "components/policy/proto/cloud_policy.pb.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/reporting/client/mock_report_queue.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/clipboard_buffer.h"
#include "ui/base/clipboard/clipboard_data.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/test/event_generator.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/test/widget_activation_waiter.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"

namespace policy {

namespace {

constexpr char kClipboardText1[] = "Hello World";
constexpr char16_t kClipboardText116[] = u"Hello World";
constexpr char16_t kClipboardText2[] = u"abcdef";

constexpr char kMailUrl[] = "https://mail.google.com";
constexpr char kDocsUrl[] = "https://docs.google.com";
constexpr char kExampleUrl[] = "https://example.com";

constexpr char kRuleName1[] = "rule #1";
constexpr char kRuleId1[] = "testid1";
const DlpRulesManager::RuleMetadata kRuleMetadata1(kRuleName1, kRuleId1);

constexpr char kRuleName2[] = "rule #2";
constexpr char kRuleId2[] = "testid2";

class FakeClipboardNotifier : public DlpClipboardNotifier {
 public:
  views::Widget* GetWidget() { return widget_.get(); }

  void ProceedPressed(std::unique_ptr<ui::ClipboardData> data,
                      const ui::DataTransferEndpoint& data_dst,
                      base::OnceClosure reporting_cb) {
    DlpClipboardNotifier::ProceedPressed(std::move(data), data_dst,
                                         std::move(reporting_cb), GetWidget());
  }

  void BlinkProceedPressed(const ui::DataTransferEndpoint& data_dst) {
    DlpClipboardNotifier::BlinkProceedPressed(data_dst, GetWidget());
  }

  void CancelWarningPressed(const ui::DataTransferEndpoint& data_dst) {
    DlpClipboardNotifier::CancelWarningPressed(data_dst, GetWidget());
  }
};

class FakeDlpController : public DataTransferDlpController,
                          public views::WidgetObserver {
 public:
  FakeDlpController(const DlpRulesManager& dlp_rules_manager,
                    FakeClipboardNotifier* helper)
      : DataTransferDlpController(dlp_rules_manager), helper_(helper) {
    DCHECK(helper);
  }

  ~FakeDlpController() override {
    auto* widget = helper_->GetWidget();
    if (widget) {
      widget->RemoveObserver(this);
    }
  }

  void NotifyBlockedPaste(
      base::optional_ref<const ui::DataTransferEndpoint> data_src,
      base::optional_ref<const ui::DataTransferEndpoint> data_dst) override {
    helper_->NotifyBlockedAction(data_src, data_dst);
  }

  void WarnOnPaste(base::optional_ref<const ui::DataTransferEndpoint> data_src,
                   base::optional_ref<const ui::DataTransferEndpoint> data_dst,
                   base::OnceClosure reporting_cb) override {
    helper_->WarnOnPaste(data_src, data_dst, std::move(reporting_cb));
  }

  void WarnOnBlinkPaste(
      base::optional_ref<const ui::DataTransferEndpoint> data_src,
      base::optional_ref<const ui::DataTransferEndpoint> data_dst,
      content::WebContents* web_contents,
      base::OnceCallback<void(bool)> paste_cb) override {
    blink_data_dst_.emplace(*data_dst);
    helper_->WarnOnBlinkPaste(data_src, data_dst, web_contents,
                              std::move(paste_cb));
  }

  bool ShouldPasteOnWarn(
      base::optional_ref<const ui::DataTransferEndpoint> data_dst) override {
    if (force_paste_on_warn_) {
      return true;
    }
    return helper_->DidUserApproveDst(data_dst);
  }

  bool ObserveWidget() {
    auto* widget = helper_->GetWidget();
    if (widget && !widget->HasObserver(this)) {
      widget->AddObserver(this);
      return true;
    }
    return false;
  }

  void ReportWarningProceededEvent(
      base::optional_ref<const ui::DataTransferEndpoint> data_src,
      base::optional_ref<const ui::DataTransferEndpoint> data_dst,
      const std::string& src_pattern,
      const std::string& dst_pattern,
      const DlpRulesManager::RuleMetadata& rule_metadata,
      bool is_clipboard_event) {
    DataTransferDlpController::ReportWarningProceededEvent(
        data_src, data_dst, src_pattern, dst_pattern, is_clipboard_event,
        rule_metadata);
  }

  raw_ptr<FakeClipboardNotifier> helper_ = nullptr;
  std::optional<ui::DataTransferEndpoint> blink_data_dst_;
  bool force_paste_on_warn_ = false;

 protected:
  base::TimeDelta GetSkipReportingTimeout() override {
    // Override with a very high value to ensure that tests are passing on slow
    // debug builds.
    return base::Milliseconds(1000);
  }
};

class MockDlpRulesManager : public DlpRulesManagerImpl {
 public:
  explicit MockDlpRulesManager(PrefService* local_state, Profile* profile)
      : DlpRulesManagerImpl(local_state, profile) {}
  ~MockDlpRulesManager() override = default;

  MOCK_CONST_METHOD0(GetReportingManager,
                     data_controls::DlpReportingManager*());
};

void SetClipboardText(std::u16string text,
                      std::unique_ptr<ui::DataTransferEndpoint> source) {
  ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste,
                                   source ? std::move(source) : nullptr);
  writer.WriteText(text);
}

// On Widget Closing, a task for NativeWidgetAura::CloseNow() gets posted. This
// task runs after the widget is destroyed which leads to a crash, that's why
// we need to flush the message loop.
void FlushMessageLoop() {
  base::RunLoop run_loop;
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, run_loop.QuitClosure());
  run_loop.Run();
}

}  // namespace

class DataTransferDlpBrowserTest : public InProcessBrowserTest {
 public:
  DataTransferDlpBrowserTest() = default;

  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();
    test_reporting_ = ::reporting::ReportingClient::TestEnvironment::
        CreateWithStorageModule();

    policy::DlpRulesManagerFactory::GetInstance()->SetTestingFactory(
        browser()->profile(),
        base::BindRepeating(&DataTransferDlpBrowserTest::SetDlpRulesManager,
                            base::Unretained(this)));
    ASSERT_TRUE(DlpRulesManagerFactory::GetForPrimaryProfile());

    reporting_manager_ = std::make_unique<data_controls::DlpReportingManager>();
    auto reporting_queue = std::unique_ptr<::reporting::MockReportQueue,
                                           base::OnTaskRunnerDeleter>(
        new ::reporting::MockReportQueue(),
        base::OnTaskRunnerDeleter(
            std::move(base::SequencedTaskRunner::GetCurrentDefault())));
    reporting_queue_ = reporting_queue.get();
    reporting_manager_->SetReportQueueForTest(std::move(reporting_queue));
    ON_CALL(*rules_manager_, GetReportingManager)
        .WillByDefault(::testing::Return(reporting_manager_.get()));

    dlp_controller_ =
        std::make_unique<FakeDlpController>(*rules_manager_, &helper_);
  }

  std::unique_ptr<KeyedService> SetDlpRulesManager(
      content::BrowserContext* context) {
    auto mock_rules_manager =
        std::make_unique<testing::NiceMock<MockDlpRulesManager>>(
            g_browser_process->local_state(),
            Profile::FromBrowserContext(context));
    rules_manager_ = mock_rules_manager.get();
    return mock_rules_manager;
  }

  void TearDownOnMainThread() override {
    reporting_queue_ = nullptr;
    dlp_controller_.reset();
    reporting_manager_.reset();
    test_reporting_.reset();
  }

  void SetupTextfield() {
    // Create a widget containing a single, focusable textfield.
    widget_ = std::make_unique<views::Widget>();

    views::Widget::InitParams params(
        views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
        views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
    widget_->Init(std::move(params));
    textfield_ = widget_->SetContentsView(std::make_unique<views::Textfield>());
    textfield_->GetViewAccessibility().SetName(u"Textfield");
    textfield_->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);

    // Show the widget.
    widget_->SetBounds(gfx::Rect(0, 0, 100, 100));
    widget_->Show();

    widget_->Show();
    views::test::WaitForWidgetActive(widget_.get(), true);

    ASSERT_TRUE(widget_->IsActive());

    // Focus the textfield and confirm initial state.
    textfield_->RequestFocus();
    ASSERT_TRUE(textfield_->HasFocus());
    ASSERT_TRUE(textfield_->GetText().empty());

    event_generator_ = std::make_unique<ui::test::EventGenerator>(
        widget_->GetNativeWindow()->GetRootWindow());
  }

  // Expects `event` to be reported then quits `run_loop`.
  void ExpectEventTobeReported(DlpPolicyEvent expected_event,
                               base::RunLoop& run_loop) {
    EXPECT_CALL(*reporting_queue_, AddRecord)
        .WillOnce([&run_loop, expected_event](
                      std::string_view record, ::reporting::Priority priority,
                      ::reporting::ReportQueue::EnqueueCallback callback) {
          DlpPolicyEvent event;
          ASSERT_TRUE(event.ParseFromString(std::string(record)));
          EXPECT_THAT(event, data_controls::IsDlpPolicyEvent(expected_event));
          std::move(callback).Run(::reporting::Status::StatusOK());
          run_loop.Quit();
        });
  }

  std::unique_ptr<::reporting::ReportingClient::TestEnvironment>
      test_reporting_;
  raw_ptr<MockDlpRulesManager, DanglingUntriaged> rules_manager_;
  std::unique_ptr<data_controls::DlpReportingManager> reporting_manager_;
  raw_ptr<::reporting::MockReportQueue> reporting_queue_;
  FakeClipboardNotifier helper_;
  std::unique_ptr<FakeDlpController> dlp_controller_;
  std::unique_ptr<ui::test::EventGenerator> event_generator_;
  std::unique_ptr<views::Widget> widget_;
  raw_ptr<views::Textfield, DanglingUntriaged> textfield_ = nullptr;
};

IN_PROC_BROWSER_TEST_F(DataTransferDlpBrowserTest, EmptyPolicy) {
  SetClipboardText(kClipboardText116, nullptr);

  ui::DataTransferEndpoint data_dst((GURL("https://google.com")));
  std::u16string result;
  ui::Clipboard::GetForCurrentThread()->ReadText(
      ui::ClipboardBuffer::kCopyPaste, &data_dst, &result);
  EXPECT_EQ(kClipboardText116, result);
}

IN_PROC_BROWSER_TEST_F(DataTransferDlpBrowserTest, BlockDestination) {
  {  // Do not remove the brackets, policy update is triggered on
     // ScopedListPrefUpdate destructor.
    dlp_test_util::DlpRule rule1(kRuleName1, "Block Gmail", kRuleId1);
    rule1.AddSrcUrl(kMailUrl).AddDstUrl("*").AddRestriction(
        data_controls::kRestrictionClipboard, data_controls::kLevelBlock);
    dlp_test_util::DlpRule rule2(kRuleName2, "Allow Gmail for work purposes",
                                 kRuleId2);
    rule2.AddSrcUrl(kMailUrl).AddDstUrl(kDocsUrl).AddRestriction(
        data_controls::kRestrictionClipboard, data_controls::kLevelAllow);

    ScopedListPrefUpdate update(g_browser_process->local_state(),
                                policy_prefs::kDlpRulesList);
    update->Append(rule1.Create());
    update->Append(rule2.Create());
  }

  SetClipboardText(
      kClipboardText116,
      std::make_unique<ui::DataTransferEndpoint>((GURL(kMailUrl))));

  ui::DataTransferEndpoint data_dst1((GURL(kMailUrl)));
  std::u16string result1;
  ui::Clipboard::GetForCurrentThread()->ReadText(
      ui::ClipboardBuffer::kCopyPaste, &data_dst1, &result1);
  EXPECT_EQ(kClipboardText116, result1);

  ui::DataTransferEndpoint data_dst2((GURL(kDocsUrl)));
  std::u16string result2;
  ui::Clipboard::GetForCurrentThread()->ReadText(
      ui::ClipboardBuffer::kCopyPaste, &data_dst2, &result2);
  EXPECT_EQ(kClipboardText116, result2);

  base::RunLoop run_loop;
  ExpectEventTobeReported(
      CreateDlpPolicyEvent(GURL(kMailUrl).spec(), GURL(kExampleUrl).spec(),
                           DlpRulesManager::Restriction::kClipboard, kRuleName1,
                           kRuleId1, DlpRulesManager::Level::kBlock),
      run_loop);
  ui::DataTransferEndpoint data_dst3((GURL(kExampleUrl)));
  std::u16string result3;
  ui::Clipboard::GetForCurrentThread()->ReadText(
      ui::ClipboardBuffer::kCopyPaste, &data_dst3, &result3);
  EXPECT_EQ(std::u16string(), result3);
  ASSERT_TRUE(dlp_controller_->ObserveWidget());
  run_loop.Run();

  SetClipboardText(
      kClipboardText116,
      std::make_unique<ui::DataTransferEndpoint>((GURL(kExampleUrl))));

  ui::DataTransferEndpoint data_dst4((GURL(kMailUrl)));
  std::u16string result4;
  ui::Clipboard::GetForCurrentThread()->ReadText(
      ui::ClipboardBuffer::kCopyPaste, &data_dst1, &result4);
  EXPECT_EQ(kClipboardText116, result4);

  FlushMessageLoop();
}

// TODO(b/278719678): Enable on Lacros.
#if BUILDFLAG(IS_CHROMEOS_LACROS) && !BUILDFLAG(IS_CHROMEOS_DEVICE)
#define MAYBE_WarnDestination DISABLED_WarnDestination
#else
#define MAYBE_WarnDestination WarnDestination
#endif
IN_PROC_BROWSER_TEST_F(DataTransferDlpBrowserTest, MAYBE_WarnDestination) {
  base::WeakPtr<views::Widget> widget;

  {  // Do not remove the brackets, policy update is triggered on
     // ScopedListPrefUpdate destructor.
    dlp_test_util::DlpRule rule(kRuleName1, "description", kRuleId1);
    rule.AddSrcUrl(kMailUrl).AddDstUrl("*").AddRestriction(
        data_controls::kRestrictionClipboard, data_controls::kLevelWarn);
    ScopedListPrefUpdate update(g_browser_process->local_state(),
                                policy_prefs::kDlpRulesList);
    update->Append(rule.Create());
  }

  SetClipboardText(
      kClipboardText116,
      std::make_unique<ui::DataTransferEndpoint>((GURL(kMailUrl))));

  SetupTextfield();
  // Initiate a paste on textfield_.
  {
    base::RunLoop run_loop;
    ExpectEventTobeReported(
        CreateDlpPolicyEvent(GURL(kMailUrl).spec(), "*",
                             DlpRulesManager::Restriction::kClipboard,
                             kRuleName1, kRuleId1,
                             DlpRulesManager::Level::kWarn),
        run_loop);
    event_generator_->PressAndReleaseKeyAndModifierKeys(ui::VKEY_V,
                                                        ui::EF_CONTROL_DOWN);

    EXPECT_EQ("", base::UTF16ToUTF8(textfield_->GetText()));
    ASSERT_TRUE(dlp_controller_->ObserveWidget());
    widget = helper_.GetWidget()->GetWeakPtr();
    EXPECT_FALSE(widget->IsClosed());

    run_loop.Run();
  }

  // Accept warning.
  {
    base::RunLoop run_loop;
    ExpectEventTobeReported(
        CreateDlpPolicyWarningProceededEvent(
            GURL(kMailUrl).spec(), "*",
            DlpRulesManager::Restriction::kClipboard, kRuleName1, kRuleId1),
        run_loop);
    ui::DataTransferEndpoint default_endpoint(ui::EndpointType::kDefault);
    auto data_src =
        std::make_unique<ui::DataTransferEndpoint>((GURL(kMailUrl)));
    auto reporting_cb =
        base::BindOnce(&FakeDlpController::ReportWarningProceededEvent,
                       base::Unretained(dlp_controller_.get()), data_src.get(),
                       &default_endpoint, kMailUrl, "*", kRuleMetadata1, true);
    auto data = std::make_unique<ui::ClipboardData>();
    data->set_text(base::UTF16ToUTF8(std::u16string(kClipboardText116)));
    helper_.ProceedPressed(std::move(data), default_endpoint,
                           std::move(reporting_cb));
    EXPECT_TRUE(!widget || widget->IsClosed());
    EXPECT_EQ(kClipboardText116, textfield_->GetText());
    run_loop.Run();
  }

  // Initiate a paste on textfield_.
  {
    base::RunLoop run_loop;
    ExpectEventTobeReported(
        CreateDlpPolicyEvent(GURL(kMailUrl).spec(), "*",
                             DlpRulesManager::Restriction::kClipboard,
                             kRuleName1, kRuleId1,
                             DlpRulesManager::Level::kWarn),
        run_loop);
    SetClipboardText(
        kClipboardText2,
        std::make_unique<ui::DataTransferEndpoint>((GURL(kMailUrl))));
    textfield_->SetText(std::u16string());
    textfield_->RequestFocus();
    event_generator_->PressAndReleaseKeyAndModifierKeys(ui::VKEY_V,
                                                        ui::EF_CONTROL_DOWN);
    EXPECT_EQ("", base::UTF16ToUTF8(textfield_->GetText()));
    ASSERT_TRUE(dlp_controller_->ObserveWidget());
    widget = helper_.GetWidget()->GetWeakPtr();
    EXPECT_FALSE(widget->IsClosed());
    run_loop.Run();
  }

  // Initiate a paste on nullptr data_dst.
  {
    std::u16string result;
    ui::Clipboard::GetForCurrentThread()->ReadText(
        ui::ClipboardBuffer::kCopyPaste, nullptr, &result);
    EXPECT_TRUE(!widget || widget->IsClosed());

    EXPECT_EQ(std::u16string(), result);
    ASSERT_TRUE(dlp_controller_->ObserveWidget());
    widget = helper_.GetWidget()->GetWeakPtr();
    EXPECT_FALSE(widget->IsClosed());
  }

  FlushMessageLoop();
}

// TODO(b/300198284): Reenable.
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#define MAYBE_DataTransferDlpBlinkBrowserTest \
  DISABLED_DataTransferDlpBlinkBrowserTest
#else
#define MAYBE_DataTransferDlpBlinkBrowserTest DataTransferDlpBlinkBrowserTest
#endif
class MAYBE_DataTransferDlpBlinkBrowserTest : public InProcessBrowserTest {
 public:
  MAYBE_DataTransferDlpBlinkBrowserTest() = default;
  MAYBE_DataTransferDlpBlinkBrowserTest(
      const MAYBE_DataTransferDlpBlinkBrowserTest&) = delete;
  MAYBE_DataTransferDlpBlinkBrowserTest& operator=(
      const MAYBE_DataTransferDlpBlinkBrowserTest&) = delete;
  ~MAYBE_DataTransferDlpBlinkBrowserTest() override = default;

 protected:
  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();

    TestingProfile::Builder builder;
#if BUILDFLAG(IS_CHROMEOS_LACROS)
    builder.SetIsMainProfile(true);
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
    profile_ = builder.Build();

    rules_manager_ = std::make_unique<::testing::NiceMock<MockDlpRulesManager>>(
        g_browser_process->local_state(), profile_.get());

    reporting_manager_ = std::make_unique<data_controls::DlpReportingManager>();
    auto reporting_queue = std::unique_ptr<::reporting::MockReportQueue,
                                           base::OnTaskRunnerDeleter>(
        new ::reporting::MockReportQueue(),
        base::OnTaskRunnerDeleter(
            std::move(base::SequencedTaskRunner::GetCurrentDefault())));
    reporting_queue_ = reporting_queue.get();
    reporting_manager_->SetReportQueueForTest(std::move(reporting_queue));
    ON_CALL(*rules_manager_, GetReportingManager)
        .WillByDefault(::testing::Return(reporting_manager_.get()));

    dlp_controller_ =
        std::make_unique<FakeDlpController>(*rules_manager_, &helper_);
  }

  void TearDownOnMainThread() override {
    reporting_queue_ = nullptr;
    dlp_controller_.reset();
    reporting_manager_.reset();
    rules_manager_.reset();
    profile_.reset();
  }

  content::WebContents* GetActiveWebContents() {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

  ::testing::AssertionResult ExecJs(content::WebContents* web_contents,
                                    const std::string& code) {
    return content::ExecJs(web_contents, code,
                           content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
                           /*world_id=*/1);
  }

  content::EvalJsResult EvalJs(content::WebContents* web_contents,
                               const std::string& code) {
    return content::EvalJs(web_contents, code,
                           content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
                           /*world_id=*/1);
  }

  // Expects `event` to be reported then quits `run_loop`.
  void ExpectEventTobeReported(DlpPolicyEvent expected_event,
                               base::RunLoop& run_loop) {
    EXPECT_CALL(*reporting_queue_, AddRecord)
        .WillOnce([&run_loop, expected_event](
                      std::string_view record, ::reporting::Priority priority,
                      ::reporting::ReportQueue::EnqueueCallback callback) {
          DlpPolicyEvent event;
          ASSERT_TRUE(event.ParseFromString(std::string(record)));
          EXPECT_THAT(event, data_controls::IsDlpPolicyEvent(expected_event));
          std::move(callback).Run(::reporting::Status::StatusOK());
          run_loop.Quit();
        });
  }

  std::unique_ptr<TestingProfile> profile_;
  std::unique_ptr<::testing::NiceMock<MockDlpRulesManager>> rules_manager_;
  std::unique_ptr<data_controls::DlpReportingManager> reporting_manager_;
  raw_ptr<::reporting::MockReportQueue> reporting_queue_;
  FakeClipboardNotifier helper_;
  std::unique_ptr<FakeDlpController> dlp_controller_;
};

IN_PROC_BROWSER_TEST_F(MAYBE_DataTransferDlpBlinkBrowserTest, ProceedOnWarn) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/title1.html")));

  {  // Do not remove the brackets, policy update is triggered on
     // ScopedListPrefUpdate destructor.
    dlp_test_util::DlpRule rule(kRuleName1, "description", kRuleId1);
    rule.AddSrcUrl(kMailUrl).AddDstUrl("*").AddRestriction(
        data_controls::kRestrictionClipboard, data_controls::kLevelWarn);
    ScopedListPrefUpdate update(g_browser_process->local_state(),
                                policy_prefs::kDlpRulesList);
    update->Append(rule.Create());
  }

  SetClipboardText(
      kClipboardText116,
      std::make_unique<ui::DataTransferEndpoint>((GURL(kMailUrl))));

  EXPECT_TRUE(
      ExecJs(GetActiveWebContents(),
             "var p = new Promise((resolve, reject) => {"
             "  window.document.onpaste = async (event) => {"
             "    if (event.clipboardData.items.length !== 1) {"
             "      reject('There were ' + event.clipboardData.items.length +"
             "             ' clipboard items. Expected 1.');"
             "    }"
             "    if (event.clipboardData.items[0].kind != 'string') {"
             "      reject('The clipboard item was of kind: ' +"
             "             event.clipboardData.items[0].kind + '. Expected ' +"
             "             'string.');"
             "    }"
             "    const clipboardDataItem = event.clipboardData.items[0];"
             "    clipboardDataItem.getAsString((clipboardDataText)=> {"
             "      resolve(clipboardDataText);});"
             "  };"
             "});"));

  content::SimulateMouseClick(GetActiveWebContents(), 0,
                              blink::WebPointerProperties::Button::kLeft);

  // Send paste event and wait till the event is reported.
  {
    base::RunLoop run_loop;
    ExpectEventTobeReported(
        CreateDlpPolicyEvent(
            GURL(kMailUrl).spec(),
            embedded_test_server()->GetURL("/title1.html").spec(),
            DlpRulesManager::Restriction::kClipboard, kRuleName1, kRuleId1,
            DlpRulesManager::Level::kWarn),
        run_loop);
    GetActiveWebContents()->Paste();
    run_loop.Run();

    ASSERT_TRUE(dlp_controller_->ObserveWidget());
    base::WeakPtr<views::Widget> widget = helper_.GetWidget()->GetWeakPtr();
    EXPECT_FALSE(widget->IsClosed());
  }

  // Proceed the warning.
  {
    base::RunLoop run_loop;
    ExpectEventTobeReported(
        CreateDlpPolicyWarningProceededEvent(
            GURL(kMailUrl).spec(),
            embedded_test_server()->GetURL("/title1.html").spec(),
            DlpRulesManager::Restriction::kClipboard, kRuleName1, kRuleId1),
        run_loop);
    ASSERT_TRUE(dlp_controller_->blink_data_dst_.has_value());
    helper_.BlinkProceedPressed(dlp_controller_->blink_data_dst_.value());
    run_loop.Run();

    EXPECT_EQ(kClipboardText1, EvalJs(GetActiveWebContents(), "p"));
    ASSERT_FALSE(helper_.GetWidget());
  }
}

IN_PROC_BROWSER_TEST_F(MAYBE_DataTransferDlpBlinkBrowserTest, CancelWarn) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/title1.html")));

  {  // Do not remove the brackets, policy update is triggered on
     // ScopedListPrefUpdate destructor.
    dlp_test_util::DlpRule rule(kRuleName1, "description", kRuleId1);
    rule.AddSrcUrl(kMailUrl).AddDstUrl("*").AddRestriction(
        data_controls::kRestrictionClipboard, data_controls::kLevelWarn);
    ScopedListPrefUpdate update(g_browser_process->local_state(),
                                policy_prefs::kDlpRulesList);
    update->Append(rule.Create());
  }

  SetClipboardText(
      kClipboardText116,
      std::make_unique<ui::DataTransferEndpoint>((GURL(kMailUrl))));

  EXPECT_TRUE(
      ExecJs(GetActiveWebContents(),
             "var p = new Promise((resolve, reject) => {"
             "  window.document.onpaste = async (event) => {"
             "    if (event.clipboardData.items.length !== 1) {"
             "      reject('There were ' + event.clipboardData.items.length +"
             "             ' clipboard items. Expected 1.');"
             "    }"
             "    if (event.clipboardData.items[0].kind != 'string') {"
             "      reject('The clipboard item was of kind: ' +"
             "             event.clipboardData.items[0].kind + '. Expected ' +"
             "             'string.');"
             "    }"
             "    const clipboardDataItem = event.clipboardData.items[0];"
             "    clipboardDataItem.getAsString((clipboardDataText)=> {"
             "      resolve(clipboardDataText);});"
             "  };"
             "});"));
  content::SimulateMouseClick(GetActiveWebContents(), 0,
                              blink::WebPointerProperties::Button::kLeft);

  // Send paste event and wait till the event is reported.
  {
    base::RunLoop run_loop;
    ExpectEventTobeReported(
        CreateDlpPolicyEvent(
            GURL(kMailUrl).spec(),
            embedded_test_server()->GetURL("/title1.html").spec(),
            DlpRulesManager::Restriction::kClipboard, kRuleName1, kRuleId1,
            DlpRulesManager::Level::kWarn),
        run_loop);
    GetActiveWebContents()->Paste();
    run_loop.Run();

    ASSERT_TRUE(dlp_controller_->ObserveWidget());
    base::WeakPtr<views::Widget> widget = helper_.GetWidget()->GetWeakPtr();
    EXPECT_FALSE(widget->IsClosed());
  }

  // Cancel the warning.
  {
    ASSERT_TRUE(dlp_controller_->blink_data_dst_.has_value());
    helper_.CancelWarningPressed(dlp_controller_->blink_data_dst_.value());

    EXPECT_EQ("", EvalJs(GetActiveWebContents(), "p"));
    ASSERT_FALSE(helper_.GetWidget());
  }
}

IN_PROC_BROWSER_TEST_F(MAYBE_DataTransferDlpBlinkBrowserTest,
                       ShouldProceedWarn) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/title1.html")));

  {  // Do not remove the brackets, policy update is triggered on
     // ScopedListPrefUpdate destructor.
    dlp_test_util::DlpRule rule(kRuleName1, "", kRuleId1);
    rule.AddSrcUrl(kMailUrl).AddDstUrl("*").AddRestriction(
        data_controls::kRestrictionClipboard, data_controls::kLevelWarn);
    ScopedListPrefUpdate update(g_browser_process->local_state(),
                                policy_prefs::kDlpRulesList);
    update->Append(rule.Create());
  }

  SetClipboardText(
      kClipboardText116,
      std::make_unique<ui::DataTransferEndpoint>((GURL(kMailUrl))));

  EXPECT_TRUE(
      ExecJs(GetActiveWebContents(),
             "var p = new Promise((resolve, reject) => {"
             "  window.document.onpaste = async (event) => {"
             "    if (event.clipboardData.items.length !== 1) {"
             "      reject('There were ' + event.clipboardData.items.length +"
             "             ' clipboard items. Expected 1.');"
             "    }"
             "    if (event.clipboardData.items[0].kind != 'string') {"
             "      reject('The clipboard item was of kind: ' +"
             "             event.clipboardData.items[0].kind + '. Expected ' +"
             "             'string.');"
             "    }"
             "    const clipboardDataItem = event.clipboardData.items[0];"
             "    clipboardDataItem.getAsString((clipboardDataText)=> {"
             "      resolve(clipboardDataText);});"
             "  };"
             "});"));

  content::SimulateMouseClick(GetActiveWebContents(), 0,
                              blink::WebPointerProperties::Button::kLeft);

  // Send paste event and wait till the event is reported.
  {
    base::RunLoop run_loop;
    ExpectEventTobeReported(
        CreateDlpPolicyWarningProceededEvent(
            GURL(kMailUrl).spec(),
            embedded_test_server()->GetURL("/title1.html").spec(),
            DlpRulesManager::Restriction::kClipboard, kRuleName1, kRuleId1),
        run_loop);
    dlp_controller_->force_paste_on_warn_ = true;
    GetActiveWebContents()->Paste();
    run_loop.Run();

    EXPECT_FALSE(dlp_controller_->ObserveWidget());
    EXPECT_FALSE(helper_.GetWidget());
    EXPECT_EQ(kClipboardText1, EvalJs(GetActiveWebContents(), "p"));
  }
}

// Test case for crbug.com/1213143
IN_PROC_BROWSER_TEST_F(MAYBE_DataTransferDlpBlinkBrowserTest, Reporting) {
  base::HistogramTester histogram_tester;

  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/title1.html")));

  {  // Do not remove the brackets, policy update is triggered on
     // ScopedListPrefUpdate destructor.
    dlp_test_util::DlpRule rule(kRuleName1, "description", kRuleId1);
    rule.AddSrcUrl(kMailUrl).AddDstUrl("*").AddRestriction(
        data_controls::kRestrictionClipboard, data_controls::kLevelReport);
    ScopedListPrefUpdate update(g_browser_process->local_state(),
                                policy_prefs::kDlpRulesList);
    update->Append(rule.Create());
  }

  SetClipboardText(
      kClipboardText116,
      std::make_unique<ui::DataTransferEndpoint>((GURL(kMailUrl))));

  EXPECT_TRUE(
      ExecJs(GetActiveWebContents(),
             "var p = new Promise((resolve, reject) => {"
             "  window.document.onpaste = async (event) => {"
             "    if (event.clipboardData.items.length !== 1) {"
             "      reject('There were ' + event.clipboardData.items.length +"
             "             ' clipboard items. Expected 1.');"
             "    }"
             "    if (event.clipboardData.items[0].kind != 'string') {"
             "      reject('The clipboard item was of kind: ' +"
             "             event.clipboardData.items[0].kind + '. Expected ' +"
             "             'string.');"
             "    }"
             "    const clipboardDataItem = event.clipboardData.items[0];"
             "    clipboardDataItem.getAsString((clipboardDataText)=> {"
             "      resolve(clipboardDataText);});"
             "  };"
             "});"));

  content::SimulateMouseClick(GetActiveWebContents(), 0,
                              blink::WebPointerProperties::Button::kLeft);

  // Send paste event and wait till the event is reported.
  {
    base::RunLoop run_loop;
    ExpectEventTobeReported(
        CreateDlpPolicyEvent(
            GURL(kMailUrl).spec(),
            embedded_test_server()->GetURL("/title1.html").spec(),
            DlpRulesManager::Restriction::kClipboard, kRuleName1, kRuleId1,
            DlpRulesManager::Level::kReport),
        run_loop);
    GetActiveWebContents()->Paste();
    run_loop.Run();

    EXPECT_FALSE(dlp_controller_->ObserveWidget());
    EXPECT_EQ(kClipboardText1, EvalJs(GetActiveWebContents(), "p"));
  }
  // TODO(b/259179332): This EXPECT_GE is always true, because it is compared to
  // 0. The histogram sum may not have any samples when the time difference is
  // very small (almost 0), because UmaHistogramTimes requires the time
  // difference to be >= 1.
  EXPECT_GE(histogram_tester.GetTotalSum(
                data_controls::GetDlpHistogramPrefix() +
                data_controls::dlp::kDataTransferReportingTimeDiffUMA),
            0);
}

}  // namespace policy